From 83e75bec052d715e09aad9296e1dc2c02b67338a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Sun, 26 Dec 2021 17:42:43 +0100 Subject: [PATCH] initial commit --- .gitignore | 14 + LICENSE.txt | 202 + build.gradle | 40 + gradle.properties | 12 + gradle/compile/java.gradle | 43 + gradle/documentation/asciidoc.gradle | 55 + gradle/ide/idea.gradle | 13 + gradle/publishing/publication.gradle | 66 + gradle/publishing/sonatype.gradle | 11 + gradle/repositories/maven.gradle | 4 + gradle/test/junit5.gradle | 27 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 + gradlew.bat | 89 + settings.gradle | 0 src/main/java/org/xbib/event/Event.java | 14 + .../java/org/xbib/event/EventConsumer.java | 4 + .../java/org/xbib/event/EventService.java | 4 + .../java/org/xbib/event/FileFollowEvent.java | 4 + .../async/AbstractAsyncFileReaderLines.java | 173 + .../org/xbib/event/async/AddOnComplete.java | 20 + .../java/org/xbib/event/async/AddOnError.java | 30 + .../java/org/xbib/event/async/AddOnNext.java | 20 + .../org/xbib/event/async/AddOnSubscribe.java | 22 + .../org/xbib/event/async/AsyncFileQuery.java | 74 + .../event/async/AsyncFileReaderBytes.java | 64 + .../event/async/AsyncFileReaderLines.java | 178 + .../org/xbib/event/async/AsyncFileWriter.java | 119 + .../java/org/xbib/event/async/AsyncFiles.java | 256 + .../org/xbib/event/async/EmptySubscriber.java | 28 + .../xbib/event/async/SubscriberBuilder.java | 47 + .../org/xbib/event/async/Subscribers.java | 13 + .../java/org/xbib/event/clock/ClockEvent.java | 12 + .../xbib/event/clock/ClockEventConsumer.java | 18 + .../xbib/event/clock/ClockEventManager.java | 61 + .../xbib/event/clock/ClockEventService.java | 37 + .../persistence/AbstractPersistenceStore.java | 80 + .../org/xbib/event/persistence/Codec.java | 10 + .../xbib/event/persistence/Durability.java | 6 + .../xbib/event/persistence/FileJsonCodec.java | 54 + .../persistence/FilePersistenceStore.java | 24 + .../event/persistence/PersistenceStore.java | 21 + .../java/org/xbib/event/queue/QueueEvent.java | 38 + .../queue/path/simple/PathQueueEvent.java | 61 + .../path/simple/PathQueueEventManager.java | 151 + .../path/simple/PathQueueEventService.java | 102 + .../queue/path/watch/PathQueueEvent.java | 61 + .../path/watch/PathQueueEventManager.java | 168 + .../path/watch/PathQueueEventService.java | 155 + .../java/org/xbib/event/timer/TimerEvent.java | 12 + .../xbib/event/timer/TimerEventManager.java | 90 + .../xbib/event/timer/TimerEventService.java | 131 + .../java/org/xbib/event/yield/Advancer.java | 19 + .../java/org/xbib/event/yield/AsyncQuery.java | 121 + .../org/xbib/event/yield/AsyncTraverser.java | 23 + src/main/java/org/xbib/event/yield/Query.java | 535 ++ .../event/yield/TraversableFinishError.java | 11 + .../java/org/xbib/event/yield/Traverser.java | 24 + src/main/java/org/xbib/event/yield/Yield.java | 16 + .../event/yield/async/AsyncQueryDistinct.java | 28 + .../event/yield/async/AsyncQueryFilter.java | 28 + .../yield/async/AsyncQueryFlatMapConcat.java | 32 + .../yield/async/AsyncQueryFlatMapMerge.java | 38 + .../event/yield/async/AsyncQueryFork.java | 25 + .../xbib/event/yield/async/AsyncQueryMap.java | 30 + .../xbib/event/yield/async/AsyncQueryOf.java | 22 + .../yield/async/AsyncQueryOfIterator.java | 24 + .../event/yield/async/AsyncQueryOnNext.java | 26 + .../event/yield/async/AsyncQuerySkip.java | 33 + .../yield/async/AsyncQueryTakeWhile.java | 60 + .../org/xbib/event/yield/boxes/BoolBox.java | 31 + .../java/org/xbib/event/yield/boxes/Box.java | 34 + .../org/xbib/event/yield/boxes/DoubleBox.java | 39 + .../org/xbib/event/yield/boxes/IntBox.java | 38 + .../org/xbib/event/yield/boxes/LongBox.java | 38 + .../java/org/xbib/event/yield/ops/Concat.java | 29 + .../org/xbib/event/yield/ops/Distinct.java | 38 + .../org/xbib/event/yield/ops/DropWhile.java | 55 + .../java/org/xbib/event/yield/ops/Filter.java | 44 + .../org/xbib/event/yield/ops/FlatMap.java | 39 + .../org/xbib/event/yield/ops/FromArray.java | 42 + .../org/xbib/event/yield/ops/FromList.java | 26 + .../org/xbib/event/yield/ops/FromStream.java | 28 + .../org/xbib/event/yield/ops/Generate.java | 29 + .../org/xbib/event/yield/ops/Iterate.java | 33 + .../java/org/xbib/event/yield/ops/Limit.java | 38 + .../org/xbib/event/yield/ops/Mapping.java | 30 + .../java/org/xbib/event/yield/ops/Peek.java | 36 + .../java/org/xbib/event/yield/ops/Skip.java | 43 + .../org/xbib/event/yield/ops/TakeWhile.java | 44 + .../java/org/xbib/event/yield/ops/Zip.java | 43 + .../yield/primitives/dbl/DoubleAdvancer.java | 69 + .../yield/primitives/dbl/DoubleQuery.java | 583 ++ .../yield/primitives/dbl/DoubleTraverser.java | 74 + .../yield/primitives/dbl/DoubleYield.java | 16 + .../primitives/dbl/ops/DoubleConcat.java | 29 + .../primitives/dbl/ops/DoubleDistinct.java | 41 + .../primitives/dbl/ops/DoubleDropWhile.java | 54 + .../primitives/dbl/ops/DoubleFilter.java | 45 + .../primitives/dbl/ops/DoubleFlatMap.java | 37 + .../primitives/dbl/ops/DoubleGenerate.java | 29 + .../primitives/dbl/ops/DoubleIterate.java | 40 + .../yield/primitives/dbl/ops/DoubleLimit.java | 37 + .../primitives/dbl/ops/DoubleMapToObj.java | 30 + .../primitives/dbl/ops/DoubleMapping.java | 30 + .../yield/primitives/dbl/ops/DoublePeek.java | 36 + .../yield/primitives/dbl/ops/DoubleSkip.java | 43 + .../primitives/dbl/ops/DoubleTakeWhile.java | 47 + .../yield/primitives/dbl/ops/DoubleZip.java | 43 + .../primitives/dbl/ops/FromDoubleArray.java | 41 + .../primitives/dbl/ops/FromDoubleStream.java | 30 + .../yield/primitives/intgr/IntAdvancer.java | 66 + .../yield/primitives/intgr/IntQuery.java | 584 ++ .../yield/primitives/intgr/IntTraverser.java | 70 + .../yield/primitives/intgr/IntYield.java | 16 + .../primitives/intgr/ops/FromIntArray.java | 41 + .../primitives/intgr/ops/FromIntStream.java | 29 + .../yield/primitives/intgr/ops/IntConcat.java | 27 + .../primitives/intgr/ops/IntDistinct.java | 39 + .../primitives/intgr/ops/IntDropWhile.java | 55 + .../yield/primitives/intgr/ops/IntFilter.java | 43 + .../primitives/intgr/ops/IntFlatMap.java | 34 + .../primitives/intgr/ops/IntGenerate.java | 29 + .../primitives/intgr/ops/IntIterate.java | 38 + .../yield/primitives/intgr/ops/IntLimit.java | 34 + .../primitives/intgr/ops/IntMapToObj.java | 29 + .../primitives/intgr/ops/IntMapping.java | 29 + .../yield/primitives/intgr/ops/IntPeek.java | 34 + .../yield/primitives/intgr/ops/IntSkip.java | 44 + .../primitives/intgr/ops/IntTakeWhile.java | 47 + .../yield/primitives/intgr/ops/IntZip.java | 43 + .../yield/primitives/lng/LongAdvancer.java | 69 + .../event/yield/primitives/lng/LongQuery.java | 583 ++ .../yield/primitives/lng/LongTraverser.java | 74 + .../event/yield/primitives/lng/LongYield.java | 16 + .../primitives/lng/ops/FromLongArray.java | 41 + .../primitives/lng/ops/FromLongStream.java | 29 + .../yield/primitives/lng/ops/LongConcat.java | 27 + .../primitives/lng/ops/LongDistinct.java | 39 + .../primitives/lng/ops/LongDropWhile.java | 54 + .../yield/primitives/lng/ops/LongFilter.java | 43 + .../yield/primitives/lng/ops/LongFlatMap.java | 35 + .../primitives/lng/ops/LongGenerate.java | 28 + .../yield/primitives/lng/ops/LongIterate.java | 38 + .../yield/primitives/lng/ops/LongLimit.java | 34 + .../yield/primitives/lng/ops/LongMap.java | 29 + .../primitives/lng/ops/LongMapToObj.java | 29 + .../yield/primitives/lng/ops/LongPeek.java | 34 + .../yield/primitives/lng/ops/LongSkip.java | 41 + .../primitives/lng/ops/LongTakeWhile.java | 44 + .../yield/primitives/lng/ops/LongZip.java | 40 + .../event/async/test/AsyncFileReaderTest.java | 400 + .../event/async/test/AsyncFileWriterTest.java | 108 + .../event/async/test/AsyncFilesFailures.java | 201 + .../Metamorphosis-by-Franz-Kafka.txt | 2362 ++++++ .../resources/The-Wizard-by-Rider-Haggard.txt | 6718 +++++++++++++++++ src/test/resources/UTF-8-test.txt | Bin 0 -> 22781 bytes 158 files changed, 18850 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/compile/java.gradle create mode 100644 gradle/documentation/asciidoc.gradle create mode 100644 gradle/ide/idea.gradle create mode 100644 gradle/publishing/publication.gradle create mode 100644 gradle/publishing/sonatype.gradle create mode 100644 gradle/repositories/maven.gradle create mode 100644 gradle/test/junit5.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/org/xbib/event/Event.java create mode 100644 src/main/java/org/xbib/event/EventConsumer.java create mode 100644 src/main/java/org/xbib/event/EventService.java create mode 100644 src/main/java/org/xbib/event/FileFollowEvent.java create mode 100644 src/main/java/org/xbib/event/async/AbstractAsyncFileReaderLines.java create mode 100644 src/main/java/org/xbib/event/async/AddOnComplete.java create mode 100644 src/main/java/org/xbib/event/async/AddOnError.java create mode 100644 src/main/java/org/xbib/event/async/AddOnNext.java create mode 100644 src/main/java/org/xbib/event/async/AddOnSubscribe.java create mode 100644 src/main/java/org/xbib/event/async/AsyncFileQuery.java create mode 100644 src/main/java/org/xbib/event/async/AsyncFileReaderBytes.java create mode 100644 src/main/java/org/xbib/event/async/AsyncFileReaderLines.java create mode 100644 src/main/java/org/xbib/event/async/AsyncFileWriter.java create mode 100644 src/main/java/org/xbib/event/async/AsyncFiles.java create mode 100644 src/main/java/org/xbib/event/async/EmptySubscriber.java create mode 100644 src/main/java/org/xbib/event/async/SubscriberBuilder.java create mode 100644 src/main/java/org/xbib/event/async/Subscribers.java create mode 100644 src/main/java/org/xbib/event/clock/ClockEvent.java create mode 100644 src/main/java/org/xbib/event/clock/ClockEventConsumer.java create mode 100644 src/main/java/org/xbib/event/clock/ClockEventManager.java create mode 100644 src/main/java/org/xbib/event/clock/ClockEventService.java create mode 100644 src/main/java/org/xbib/event/persistence/AbstractPersistenceStore.java create mode 100644 src/main/java/org/xbib/event/persistence/Codec.java create mode 100644 src/main/java/org/xbib/event/persistence/Durability.java create mode 100644 src/main/java/org/xbib/event/persistence/FileJsonCodec.java create mode 100644 src/main/java/org/xbib/event/persistence/FilePersistenceStore.java create mode 100644 src/main/java/org/xbib/event/persistence/PersistenceStore.java create mode 100644 src/main/java/org/xbib/event/queue/QueueEvent.java create mode 100644 src/main/java/org/xbib/event/queue/path/simple/PathQueueEvent.java create mode 100644 src/main/java/org/xbib/event/queue/path/simple/PathQueueEventManager.java create mode 100644 src/main/java/org/xbib/event/queue/path/simple/PathQueueEventService.java create mode 100644 src/main/java/org/xbib/event/queue/path/watch/PathQueueEvent.java create mode 100644 src/main/java/org/xbib/event/queue/path/watch/PathQueueEventManager.java create mode 100644 src/main/java/org/xbib/event/queue/path/watch/PathQueueEventService.java create mode 100644 src/main/java/org/xbib/event/timer/TimerEvent.java create mode 100644 src/main/java/org/xbib/event/timer/TimerEventManager.java create mode 100644 src/main/java/org/xbib/event/timer/TimerEventService.java create mode 100644 src/main/java/org/xbib/event/yield/Advancer.java create mode 100644 src/main/java/org/xbib/event/yield/AsyncQuery.java create mode 100644 src/main/java/org/xbib/event/yield/AsyncTraverser.java create mode 100644 src/main/java/org/xbib/event/yield/Query.java create mode 100644 src/main/java/org/xbib/event/yield/TraversableFinishError.java create mode 100644 src/main/java/org/xbib/event/yield/Traverser.java create mode 100644 src/main/java/org/xbib/event/yield/Yield.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryDistinct.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryFilter.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryFlatMapConcat.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryFlatMapMerge.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryFork.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryMap.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryOf.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryOfIterator.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryOnNext.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQuerySkip.java create mode 100644 src/main/java/org/xbib/event/yield/async/AsyncQueryTakeWhile.java create mode 100644 src/main/java/org/xbib/event/yield/boxes/BoolBox.java create mode 100644 src/main/java/org/xbib/event/yield/boxes/Box.java create mode 100644 src/main/java/org/xbib/event/yield/boxes/DoubleBox.java create mode 100644 src/main/java/org/xbib/event/yield/boxes/IntBox.java create mode 100644 src/main/java/org/xbib/event/yield/boxes/LongBox.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Concat.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Distinct.java create mode 100644 src/main/java/org/xbib/event/yield/ops/DropWhile.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Filter.java create mode 100644 src/main/java/org/xbib/event/yield/ops/FlatMap.java create mode 100644 src/main/java/org/xbib/event/yield/ops/FromArray.java create mode 100644 src/main/java/org/xbib/event/yield/ops/FromList.java create mode 100644 src/main/java/org/xbib/event/yield/ops/FromStream.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Generate.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Iterate.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Limit.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Mapping.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Peek.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Skip.java create mode 100644 src/main/java/org/xbib/event/yield/ops/TakeWhile.java create mode 100644 src/main/java/org/xbib/event/yield/ops/Zip.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/DoubleAdvancer.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/DoubleQuery.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/DoubleTraverser.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/DoubleYield.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleConcat.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleDistinct.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleDropWhile.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleFilter.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleFlatMap.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleGenerate.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleIterate.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleLimit.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleMapToObj.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleMapping.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoublePeek.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleSkip.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleTakeWhile.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleZip.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/FromDoubleArray.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/dbl/ops/FromDoubleStream.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/IntAdvancer.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/IntQuery.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/IntTraverser.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/IntYield.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/FromIntArray.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/FromIntStream.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntConcat.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntDistinct.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntDropWhile.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntFilter.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntFlatMap.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntGenerate.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntIterate.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntLimit.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntMapToObj.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntMapping.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntPeek.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntSkip.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntTakeWhile.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntZip.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/LongAdvancer.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/LongQuery.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/LongTraverser.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/LongYield.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/FromLongArray.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/FromLongStream.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongConcat.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongDistinct.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongDropWhile.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongFilter.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongFlatMap.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongGenerate.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongIterate.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongLimit.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongMap.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongMapToObj.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongPeek.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongSkip.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongTakeWhile.java create mode 100644 src/main/java/org/xbib/event/yield/primitives/lng/ops/LongZip.java create mode 100644 src/test/java/org/xbib/event/async/test/AsyncFileReaderTest.java create mode 100644 src/test/java/org/xbib/event/async/test/AsyncFileWriterTest.java create mode 100644 src/test/java/org/xbib/event/async/test/AsyncFilesFailures.java create mode 100644 src/test/resources/Metamorphosis-by-Franz-Kafka.txt create mode 100644 src/test/resources/The-Wizard-by-Rider-Haggard.txt create mode 100644 src/test/resources/UTF-8-test.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5b5511 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +/.settings +/.classpath +/.project +/.gradle +**/data +**/work +**/logs +**/.idea +**/target +**/out +**/build +.DS_Store +*.iml +*~ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..54f6611 --- /dev/null +++ b/build.gradle @@ -0,0 +1,40 @@ +plugins { + id "de.marcphilipp.nexus-publish" version "0.4.0" + id "io.codearte.nexus-staging" version "0.21.1" +} + +wrapper { + gradleVersion = "${project.property('gradle.wrapper.version')}" + distributionType = Wrapper.DistributionType.ALL +} +ext { + user = 'jprante' + name = 'event' + description = 'Event framework for Java (NIO paths, files, timers, journals)' + inceptionYear = '2021' + url = 'https://github.com/' + user + '/' + name + scmUrl = 'https://github.com/' + user + '/' + name + scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git' + scmDeveloperConnection = 'scm:git:ssh://git@github.com:' + user + '/' + name + '.git' + issueManagementSystem = 'Github' + issueManagementUrl = ext.scmUrl + '/issues' + licenseName = 'The Apache License, Version 2.0' + licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' +} + +apply plugin: 'java-library' +apply from: rootProject.file('gradle/ide/idea.gradle') +apply from: rootProject.file('gradle/compile/java.gradle') +apply from: rootProject.file('gradle/test/junit5.gradle') +apply from: rootProject.file('gradle/repositories/maven.gradle') +apply from: rootProject.file('gradle/publishing/publication.gradle') +apply from: rootProject.file('gradle/publishing/sonatype.gradle') + +dependencies { + api "org.xbib:settings-api:${project.property('xbib-content.version')}" + implementation "org.xbib:guava:${project.property('xbib-guava.version')}" + implementation "org.xbib:time:${project.property('xbib-time.version')}" + implementation "org.xbib:datastructures-json-tiny:${project.property('xbib-datastructures.version')}" + implementation "org.reactivestreams:reactive-streams:${project.property('reactivestreams.version')}" + testImplementation "io.reactivex.rxjava3:rxjava:${project.property('rxjava3.version')}" +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..0071ad3 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +group = org.xbib +name = event +version = 0.0.1 + +org.gradle.warning.mode = ALL +gradle.wrapper.version = 7.3.2 +xbib-guava.version = 30.1 +xbib-content.version = 4.0.0 +xbib-time.version = 2.1.1 +xbib-datastructures.version = 1.0.0 +reactivestreams.version = 1.0.3 +rxjava3.version = 3.0.3 diff --git a/gradle/compile/java.gradle b/gradle/compile/java.gradle new file mode 100644 index 0000000..c9bba7f --- /dev/null +++ b/gradle/compile/java.gradle @@ -0,0 +1,43 @@ + +apply plugin: 'java-library' + +java { + modularity.inferModulePath.set(true) +} + +compileJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +compileTestJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +jar { + manifest { + attributes('Implementation-Version': project.version) + } +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier 'javadoc' +} + +artifacts { + archives sourcesJar, javadocJar +} + +tasks.withType(JavaCompile) { + options.compilerArgs << '-Xlint:all,-fallthrough' +} + +javadoc { + options.addStringOption('Xdoclint:none', '-quiet') +} diff --git a/gradle/documentation/asciidoc.gradle b/gradle/documentation/asciidoc.gradle new file mode 100644 index 0000000..87ba22e --- /dev/null +++ b/gradle/documentation/asciidoc.gradle @@ -0,0 +1,55 @@ +apply plugin: 'org.xbib.gradle.plugin.asciidoctor' + +configurations { + asciidoclet +} + +dependencies { + asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}" +} + + +asciidoctor { + backends 'html5' + outputDir = file("${rootProject.projectDir}/docs") + separateOutputDirs = false + attributes 'source-highlighter': 'coderay', + idprefix: '', + idseparator: '-', + toc: 'left', + doctype: 'book', + icons: 'font', + encoding: 'utf-8', + sectlink: true, + sectanchors: true, + linkattrs: true, + imagesdir: 'img', + stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css" +} + + +/*javadoc { +options.docletpath = configurations.asciidoclet.files.asType(List) +options.doclet = 'org.asciidoctor.Asciidoclet' +//options.overview = "src/docs/asciidoclet/overview.adoc" +options.addStringOption "-base-dir", "${projectDir}" +options.addStringOption "-attribute", + "name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}" +configure(options) { + noTimestamp = true +} +}*/ + + +/*javadoc { + options.docletpath = configurations.asciidoclet.files.asType(List) + options.doclet = 'org.asciidoctor.Asciidoclet' + options.overview = "${rootProject.projectDir}/src/docs/asciidoclet/overview.adoc" + options.addStringOption "-base-dir", "${projectDir}" + options.addStringOption "-attribute", + "name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}" + options.destinationDirectory(file("${projectDir}/docs/javadoc")) + configure(options) { + noTimestamp = true + } +}*/ diff --git a/gradle/ide/idea.gradle b/gradle/ide/idea.gradle new file mode 100644 index 0000000..64e2167 --- /dev/null +++ b/gradle/ide/idea.gradle @@ -0,0 +1,13 @@ +apply plugin: 'idea' + +idea { + module { + outputDir file('build/classes/java/main') + testOutputDir file('build/classes/java/test') + } +} + +if (project.convention.findPlugin(JavaPluginConvention)) { + //sourceSets.main.output.classesDirs = file("build/classes/java/main") + //sourceSets.test.output.classesDirs = file("build/classes/java/test") +} diff --git a/gradle/publishing/publication.gradle b/gradle/publishing/publication.gradle new file mode 100644 index 0000000..2e2b2c0 --- /dev/null +++ b/gradle/publishing/publication.gradle @@ -0,0 +1,66 @@ + +apply plugin: "de.marcphilipp.nexus-publish" + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifact sourcesJar + artifact javadocJar + pom { + name = project.name + description = rootProject.ext.description + url = rootProject.ext.url + inceptionYear = rootProject.ext.inceptionYear + packaging = 'jar' + organization { + name = 'xbib' + url = 'https://xbib.org' + } + developers { + developer { + id = 'jprante' + name = 'Jörg Prante' + email = 'joergprante@gmail.com' + url = 'https://github.com/jprante' + } + } + scm { + url = rootProject.ext.scmUrl + connection = rootProject.ext.scmConnection + developerConnection = rootProject.ext.scmDeveloperConnection + } + issueManagement { + system = rootProject.ext.issueManagementSystem + url = rootProject.ext.issueManagementUrl + } + licenses { + license { + name = rootProject.ext.licenseName + url = rootProject.ext.licenseUrl + distribution = 'repo' + } + } + } + } + } +} + +if (project.hasProperty("signing.keyId")) { + apply plugin: 'signing' + signing { + sign publishing.publications.mavenJava + } +} + +if (project.hasProperty("ossrhUsername")) { + nexusPublishing { + repositories { + sonatype { + username = project.property('ossrhUsername') + password = project.property('ossrhPassword') + packageGroup = "org.xbib" + } + } + } +} diff --git a/gradle/publishing/sonatype.gradle b/gradle/publishing/sonatype.gradle new file mode 100644 index 0000000..e1813f3 --- /dev/null +++ b/gradle/publishing/sonatype.gradle @@ -0,0 +1,11 @@ + +if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) { + + apply plugin: 'io.codearte.nexus-staging' + + nexusStaging { + username = project.property('ossrhUsername') + password = project.property('ossrhPassword') + packageGroup = "org.xbib" + } +} diff --git a/gradle/repositories/maven.gradle b/gradle/repositories/maven.gradle new file mode 100644 index 0000000..ec58acb --- /dev/null +++ b/gradle/repositories/maven.gradle @@ -0,0 +1,4 @@ +repositories { + mavenLocal() + mavenCentral() +} diff --git a/gradle/test/junit5.gradle b/gradle/test/junit5.gradle new file mode 100644 index 0000000..cfef972 --- /dev/null +++ b/gradle/test/junit5.gradle @@ -0,0 +1,27 @@ + +def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.6.2' +def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2' + +dependencies { + testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}" + testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}" + testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" +} + +test { + useJUnitPlatform() + failFast = true + testLogging { + events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' + } + afterSuite { desc, result -> + if (!desc.parent) { + println "\nTest result: ${result.resultType}" + println "Test summary: ${result.testCount} tests, " + + "${result.successfulTestCount} succeeded, " + + "${result.failedTestCount} failed, " + + "${result.skippedTestCount} skipped" + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac0b842 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/org/xbib/event/Event.java b/src/main/java/org/xbib/event/Event.java new file mode 100644 index 0000000..591dd89 --- /dev/null +++ b/src/main/java/org/xbib/event/Event.java @@ -0,0 +1,14 @@ +package org.xbib.event; + +import java.util.Map; + +public interface Event { + + void setKey(String key); + + String getKey(); + + void setMap(Map map); + + Map getMap(); +} diff --git a/src/main/java/org/xbib/event/EventConsumer.java b/src/main/java/org/xbib/event/EventConsumer.java new file mode 100644 index 0000000..1aa7d09 --- /dev/null +++ b/src/main/java/org/xbib/event/EventConsumer.java @@ -0,0 +1,4 @@ +package org.xbib.event; + +public interface EventConsumer { +} diff --git a/src/main/java/org/xbib/event/EventService.java b/src/main/java/org/xbib/event/EventService.java new file mode 100644 index 0000000..c15ed96 --- /dev/null +++ b/src/main/java/org/xbib/event/EventService.java @@ -0,0 +1,4 @@ +package org.xbib.event; + +public class EventService { +} diff --git a/src/main/java/org/xbib/event/FileFollowEvent.java b/src/main/java/org/xbib/event/FileFollowEvent.java new file mode 100644 index 0000000..7ad1552 --- /dev/null +++ b/src/main/java/org/xbib/event/FileFollowEvent.java @@ -0,0 +1,4 @@ +package org.xbib.event; + +public class FileFollowEvent { +} diff --git a/src/main/java/org/xbib/event/async/AbstractAsyncFileReaderLines.java b/src/main/java/org/xbib/event/async/AbstractAsyncFileReaderLines.java new file mode 100644 index 0000000..45e6081 --- /dev/null +++ b/src/main/java/org/xbib/event/async/AbstractAsyncFileReaderLines.java @@ -0,0 +1,173 @@ +package org.xbib.event.async; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.CompletionHandler; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.InvalidParameterException; +import java.util.function.ObjIntConsumer; + +import static java.nio.channels.AsynchronousFileChannel.open; + +/** + * Asynchronous non-blocking read operations that use an underlying AsynchronousFileChannel. + */ +public abstract class AbstractAsyncFileReaderLines { + static final int BUFFER_SIZE = 4096 * 8; // the transfer buffer size + private static final int MAX_LINE_SIZE = 4096; + private static final int LF = '\n'; + private static final int CR = '\r'; + // + // This flag will track whether this `Subscription` is to be considered cancelled or not. + private boolean cancelled = false; + + /** + * Asynchronous read chunk operation, callback based. + */ + private static void readBytes( + AsynchronousFileChannel asyncFile, + long position, // current read or write position in file + byte[] data, // buffer for current producing line + int size, + ObjIntConsumer completed) { + if (completed == null) + throw new InvalidParameterException("callback can't be null!"); + if (size > data.length) + size = data.length; + if (size == 0) { + completed.accept(null, 0); + return; + } + ByteBuffer buf = ByteBuffer.wrap(data, 0, size); + CompletionHandler readCompleted = + new CompletionHandler<>() { + @Override + public void completed(Integer result, Object attachment) { + completed.accept(null, result); + } + + @Override + public void failed(Throwable exc, Object attachment) { + completed.accept(exc, 0); + } + }; + asyncFile.read(buf, position, null, readCompleted); + } + + protected abstract void onError(Throwable error); + + protected abstract void onComplete(); + + protected abstract void onProduceLine(String line); + + public final void cancel() { + cancelled = true; + } + + public final boolean isCancelled() { + return cancelled; + } + + final void readLines(Path file) throws IOException { + readLines(open(file, StandardOpenOption.READ), BUFFER_SIZE); + } + + /** + * Read bytes from an {@code AsynchronousFileChannel}, which are decoded into characters + * using the UTF-8 charset. + * The resulting characters are parsed by line and passed to the destination buffer. + * + * @param asyncFile the nio associated file channel. + * @param bufferSize + */ + final void readLines( + AsynchronousFileChannel asyncFile, + int bufferSize) { + readLines(asyncFile, 0, 0, 0, new byte[bufferSize], new byte[MAX_LINE_SIZE], 0); + } + + /** + * There is a recursion on `readLines()`establishing a serial order among: + * `readLines()` -> `produceLine()` -> `onProduceLine()` -> `readLines()` -> and so on. + * It finishes with a call to `close()`. + * + * @param asyncFile the nio associated file channel. + * @param position current read or write position in file. + * @param bufpos read position in buffer. + * @param bufsize total bytes in buffer. + * @param buffer buffer for current producing line. + * @param auxline the transfer buffer. + * @param linepos current position in producing line. + */ + private void readLines( + AsynchronousFileChannel asyncFile, + long position, // current read or write position in file + int bufpos, // read position in buffer + int bufsize, // total bytes in buffer + byte[] buffer, // buffer for current producing line + byte[] auxline, // the transfer buffer + int linepos) // current position in producing line + { + while (bufpos < bufsize) { + if (buffer[bufpos] == LF) { + if (linepos > 0 && auxline[linepos - 1] == CR) linepos--; + bufpos++; + produceLine(auxline, linepos); + linepos = 0; + } else if (linepos == MAX_LINE_SIZE - 1) { + produceLine(auxline, linepos); + linepos = 0; + } else auxline[linepos++] = buffer[bufpos++]; + } + int lastLinePos = linepos; // we need a final variable captured in the next lambda + if (!isCancelled()) readBytes(asyncFile, position, buffer, buffer.length, (err, res) -> { + if (isCancelled()) + return; + if (err != null) { + onError(err); + close(asyncFile); + return; + } + if (res <= 0) { + // needed for last line that doesn't end with LF + if (lastLinePos > 0) { + produceLine(auxline, lastLinePos); + } + // Following it will invoke onComplete() + close(asyncFile); + } else { + readLines(asyncFile, position + res, 0, res, buffer, auxline, lastLinePos); + } + }); + } + + /** + * Performed from the IO background thread when it reached the end of the file. + * + * @param asyncFile + */ + private void close(AsynchronousFileChannel asyncFile) { + try { + asyncFile.close(); + } catch (IOException e) { + onError(e); // Failed terminal state. + // Emission has finished. Does not propagate error on CompletableFuture. + } finally { + onComplete(); + } + } + + /** + * This is called only from readLines() callback and performed from a background IO thread. + * + * @param auxline the transfer buffer. + * @param linepos current position in producing line. + */ + private void produceLine(byte[] auxline, int linepos) { + String line = new String(auxline, 0, linepos, StandardCharsets.UTF_8); + onProduceLine(line); + } +} diff --git a/src/main/java/org/xbib/event/async/AddOnComplete.java b/src/main/java/org/xbib/event/async/AddOnComplete.java new file mode 100644 index 0000000..ecf9151 --- /dev/null +++ b/src/main/java/org/xbib/event/async/AddOnComplete.java @@ -0,0 +1,20 @@ +package org.xbib.event.async; + +import org.reactivestreams.Subscriber; + +public class AddOnComplete extends SubscriberBuilder { + + private final Runnable action; + + public AddOnComplete(Runnable action, Subscriber sub) { + + super(sub); + this.action = action; + } + + @Override + public void onComplete() { + super.onComplete(); + action.run(); + } +} diff --git a/src/main/java/org/xbib/event/async/AddOnError.java b/src/main/java/org/xbib/event/async/AddOnError.java new file mode 100644 index 0000000..7c40c29 --- /dev/null +++ b/src/main/java/org/xbib/event/async/AddOnError.java @@ -0,0 +1,30 @@ +package org.xbib.event.async; + +import org.reactivestreams.Subscriber; + +import java.util.function.Consumer; + +public class AddOnError extends SubscriberBuilder implements Subscriber { + + private final Consumer cons; + + public AddOnError(Consumer cons, Subscriber sub) { + super(sub); + this.cons = cons; + } + + @Override + public void onNext(T item) { + try { + super.onNext(item); + } catch (Exception err) { + this.onError(err); + } + } + + @Override + public void onError(Throwable throwable) { + super.onError(throwable); + cons.accept(throwable); + } +} diff --git a/src/main/java/org/xbib/event/async/AddOnNext.java b/src/main/java/org/xbib/event/async/AddOnNext.java new file mode 100644 index 0000000..2e27ac3 --- /dev/null +++ b/src/main/java/org/xbib/event/async/AddOnNext.java @@ -0,0 +1,20 @@ +package org.xbib.event.async; + +import org.reactivestreams.Subscriber; + +import java.util.function.Consumer; + +public class AddOnNext extends SubscriberBuilder implements Subscriber { + + private final Consumer cons; + + public AddOnNext(Consumer cons) { + super(new EmptySubscriber<>()); + this.cons = cons; + } + + @Override + public void onNext(T item) { + cons.accept(item); + } +} diff --git a/src/main/java/org/xbib/event/async/AddOnSubscribe.java b/src/main/java/org/xbib/event/async/AddOnSubscribe.java new file mode 100644 index 0000000..ee1a509 --- /dev/null +++ b/src/main/java/org/xbib/event/async/AddOnSubscribe.java @@ -0,0 +1,22 @@ +package org.xbib.event.async; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.util.function.Consumer; + +public class AddOnSubscribe extends SubscriberBuilder implements Subscriber { + + private final Consumer cons; + + public AddOnSubscribe(Consumer cons, Subscriber sub) { + super(sub); + this.cons = cons; + } + + @Override + public void onSubscribe(Subscription subscription) { + super.onSubscribe(subscription); + cons.accept(subscription); + } +} diff --git a/src/main/java/org/xbib/event/async/AsyncFileQuery.java b/src/main/java/org/xbib/event/async/AsyncFileQuery.java new file mode 100644 index 0000000..31a1bfa --- /dev/null +++ b/src/main/java/org/xbib/event/async/AsyncFileQuery.java @@ -0,0 +1,74 @@ +package org.xbib.event.async; + +import org.xbib.event.yield.AsyncQuery; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class AsyncFileQuery extends AsyncQuery { + + private final Path file; + + public AsyncFileQuery(Path file) { + this.file = file; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + /* + * The following CF is used bidirectionally from the Reader to the user and vice-versa. + * The Reader may notify the user about end completion and the user may tell the Reader + * to stop read and invoke the callback when the CF is cancelled or completed by the user. + */ + CompletableFuture cf = new CompletableFuture<>(); + try { + ReaderToCallback + .of(cons, () -> { + if (!cf.isDone()) cf.complete(null); + }) + .apply(reader -> cf.whenComplete((nothing, err) -> reader.cancel())) + .readLines(file); + } catch (IOException e) { + cf.completeExceptionally(e); + } + return cf; + } + + + private static class ReaderToCallback extends AbstractAsyncFileReaderLines { + private final BiConsumer cons; + private final Runnable doOnComplete; + + public ReaderToCallback(BiConsumer cons, Runnable doOnComplete) { + this.cons = cons; + this.doOnComplete = doOnComplete; + } + + public static ReaderToCallback of(BiConsumer cons, Runnable doOnComplete) { + return new ReaderToCallback(cons, doOnComplete); + } + + public final AbstractAsyncFileReaderLines apply(Consumer cons) { + cons.accept(this); + return this; + } + + @Override + public void onProduceLine(String line) { + cons.accept(line, null); + } + + @Override + public void onError(Throwable err) { + cons.accept(null, err); + } + + @Override + public void onComplete() { + doOnComplete.run(); + } + } +} diff --git a/src/main/java/org/xbib/event/async/AsyncFileReaderBytes.java b/src/main/java/org/xbib/event/async/AsyncFileReaderBytes.java new file mode 100644 index 0000000..227082b --- /dev/null +++ b/src/main/java/org/xbib/event/async/AsyncFileReaderBytes.java @@ -0,0 +1,64 @@ +package org.xbib.event.async; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.CompletionHandler; +import java.util.concurrent.CompletableFuture; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +public class AsyncFileReaderBytes { + + private AsyncFileReaderBytes() { + } + + static CompletableFuture readAllBytes( + AsynchronousFileChannel asyncFile, + ByteBuffer buffer, + int position, + ByteArrayOutputStream out) { + return readToByteArrayStream(asyncFile, buffer, position, out) + .thenCompose(index -> + index < 0 + ? completedFuture(position) + : readAllBytes(asyncFile, buffer.clear(), position + index, out)); + + } + + static CompletableFuture readToByteArrayStream( + AsynchronousFileChannel asyncFile, + ByteBuffer buffer, + int position, + ByteArrayOutputStream out) { + CompletableFuture promise = new CompletableFuture<>(); + asyncFile.read(buffer, position, buffer, new CompletionHandler() { + @Override + public void completed(Integer result, ByteBuffer attachment) { + if (result > 0) { + attachment.flip(); + byte[] data = new byte[attachment.limit()]; // limit = result + attachment.get(data); + write(out, data); + } + promise.complete(result); + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + promise.completeExceptionally(exc); + } + }); + return promise; + } + + static void write(ByteArrayOutputStream out, byte[] data) { + try { + out.write(data); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/main/java/org/xbib/event/async/AsyncFileReaderLines.java b/src/main/java/org/xbib/event/async/AsyncFileReaderLines.java new file mode 100644 index 0000000..0b9b1ca --- /dev/null +++ b/src/main/java/org/xbib/event/async/AsyncFileReaderLines.java @@ -0,0 +1,178 @@ +package org.xbib.event.async; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.nio.channels.AsynchronousFileChannel; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Asynchronous non-blocking read operations with a reactive based API + * implementing the Subscription proposal from reactivestreams. + */ +public class AsyncFileReaderLines extends AbstractAsyncFileReaderLines implements Subscription { + // + // We need a reference to the `Subscriber` so we can talk to it. + private final Subscriber sub; + // + // We are using this `AtomicBoolean` to make sure that this `Subscription` doesn't run concurrently with itself, + // which would violate rule 1.3 among others (no concurrent notifications). + // Possible states: 0 (not emitting), 1 (emitting lines) and 3 (evaluating conditions requests and lines). + // The onEmit changes from states 0 -> 1 <-> 3 -> 0. + // It never changes from 1 to 0 directly. It must pass before by the state 3. + private final AtomicInteger onEmit = new AtomicInteger(0); + // + // This `ConcurrentLinkedQueue` will track the `onNext` signals that will be sent to the `Subscriber`. + private final ConcurrentLinkedDeque lines = new ConcurrentLinkedDeque<>(); + // + // Here we track the current demand, i.e. what has been requested but not yet delivered. + private final AtomicLong requests = new AtomicLong(); + // + // Need to keep track of End-of-Stream + private boolean hasNext = true; + + AsyncFileReaderLines(Subscriber sub, AsynchronousFileChannel asyncFile, int bufferSize) { + this.sub = sub; + this.readLines(asyncFile, bufferSize); + } + + @Override + protected void onError(Throwable error) { + sub.onError(error); + } + + /** + * Performed from the IO background thread when it reached the end of the file. + */ + @Override + protected void onComplete() { + hasNext = false; + tryFlushPendingLines(); + } + + /** + * This is called only from readLines() callback and performed from a background IO thread. + */ + @Override + protected void onProduceLine(String line) { + /** + * Always put the newly line into lines because a concurrent request + * may be asking for new lines and we should ensure the total order. + */ + lines.offer(line); + /** + * It only emits lines if subscription is not cancelled yet and there are still + * pending requests. + */ + while (!isCancelled() // This makes sure that rule 1.8 is upheld, i.e. we need to stop signalling "eventually" + && requests.get() > 0 // This makes sure that rule 1.1 is upheld (sending more than was demanded) + && !lines.isEmpty()) { + emitLine(); + } + } + + /** + * Emit a line to the Subscriber and decrement the number of pending requests. + */ + private void emitLine() { + String line = lines.poll(); + if (line != null) { + sub.onNext(line); + requests.decrementAndGet(); + } else { + terminateDueTo(new IllegalStateException("Unexpected race occur on lines offer. No other thread should concurrently should be taking lines!")); + } + } + + /** + * Implementation of `Subscription.request` registers that more elements are in demand. + * This request() only try to emitLines() after the End-of-Stream, in which case we + * should control mutual exclusion to the emitLines(). + * + * @param l + */ + @Override + public void request(long l) { + if (isCancelled()) return; + doRequest(l); + if (!hasNext) { + tryFlushPendingLines(); + } + } + + /** + * This method may be invoked by request() in case of End-of-Stream or by close(). + * Here the `onEmit` controls mutual exclusion to the emitLine() call. + */ + private void tryFlushPendingLines() { + int state; + // Spin try for emission entry. + // If it successfully change state from 0 to 1, then it will proceed to try to emit lines. + // It quits if someone else is already emitting lines (in state 1). + // If someone else is evaluating the conditions (state 3) then it will spin and retry. + while ((state = onEmit.compareAndExchange(0, 1)) > 0) + if (state == 1) return; // give up + // + // Start emission (in state 1) + while (toContinue()) { + emitLine(); + } + // End emission (in state 3) + // Other thread entering at this moment in request() increments requests and + // this thread does not see the new value of requests and do not emit pending lines. + // Yet, the other thread will spin until onEmit change from 3 to 0 and then + // it will change onEmit to 1 and proceed emitting pending lines. + onEmit.set(0); // release onEmit + if (lines.isEmpty()) { + cancel(); // We need to consider this `Subscription` as cancelled as per rule 1.6 + sub.onComplete(); // Then we signal `onComplete` as per rule 1.2 and 1.5 + } + } + + /** + * Here it will change to state 3 (on evaluation) and then to state 1 (emitting) + * if there are pending requests and lines to be emitted. + */ + private boolean toContinue() { + // First, change to state 3 corresponding to evaluation of requests and lines. + onEmit.set(3); + boolean cont = !isCancelled() // This makes sure that rule 1.8 is upheld, i.e. we need to stop signalling "eventually" + && requests.get() > 0 // This makes sure that rule 1.1 is upheld (sending more than was demanded) + && !lines.isEmpty(); + // If there are pending requests and lines to be emitted, then change to + // state 1 that it will emit those lines. + if (cont) onEmit.set(1); + return cont; + } + + /** + * This method will register inbound demand from our `Subscriber` and validate it against rule 3.9 and rule 3.17 + */ + private void doRequest(final long n) { + if (n < 1) + terminateDueTo(new IllegalArgumentException(sub + " violated the Reactive Streams rule 3.9 by requesting a non-positive number of elements.")); + else if (requests.get() + n < 1) { + // As governed by rule 3.17, when demand overflows `Long.MAX_VALUE` we treat the signalled demand as "effectively unbounded" + requests.set(Long.MAX_VALUE); // Here we protect from the overflow and treat it as "effectively unbounded" + } else { + requests.addAndGet(n); // Here we record the downstream demand + } + } + + /** + * This is a helper method to ensure that we always `cancel` when we signal `onError` as per rule 1.6 + */ + private void terminateDueTo(final Throwable t) { + cancel(); // When we signal onError, the subscription must be considered as cancelled, as per rule 1.6 + try { + sub.onError(t); // Then we signal the error downstream, to the `Subscriber` + } catch (final Exception t2) { // If `onError` throws an exception, this is a spec violation according to rule 1.9, and all we can do is to log it. + Throwable ex = new IllegalStateException(sub + " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", t2); + Logger.getGlobal().log(Level.SEVERE, "Violated the Reactive Streams rule 2.13", ex); + } + } +} diff --git a/src/main/java/org/xbib/event/async/AsyncFileWriter.java b/src/main/java/org/xbib/event/async/AsyncFileWriter.java new file mode 100644 index 0000000..9b79e15 --- /dev/null +++ b/src/main/java/org/xbib/event/async/AsyncFileWriter.java @@ -0,0 +1,119 @@ +package org.xbib.event.async; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.CompletionHandler; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.CompletableFuture; + +import static java.nio.ByteBuffer.wrap; +import static java.nio.channels.AsynchronousFileChannel.open; + +/** + * Asynchronous non-blocking write operations with a {@code CompletableFuture} based API. + * These operations use an underlying {@code AsynchronousFileChannel}. + * All methods are asynchronous including the {@code close()} which chains a continuation + * on last resulting write {@code CompletableFuture} to close the {@code AsyncFileChannel} on completion. + */ +public class AsyncFileWriter implements AutoCloseable { + + final AsynchronousFileChannel asyncFile; + /** + * File position after last write operation completion. + */ + private CompletableFuture currentPosition = CompletableFuture.completedFuture(0); + + public AsyncFileWriter(AsynchronousFileChannel asyncFile) { + this.asyncFile = asyncFile; + } + + public AsyncFileWriter(Path file, StandardOpenOption... options) throws IOException { + this(open(file, options)); + } + + static CompletableFuture write( + AsynchronousFileChannel asyncFile, + ByteBuffer buf, + int position) { + CompletableFuture promise = new CompletableFuture<>(); + asyncFile.write(buf, position, null, new CompletionHandler() { + @Override + public void completed(Integer result, Object attachment) { + promise.complete(result); + } + + @Override + public void failed(Throwable exc, Object attachment) { + promise.completeExceptionally(exc); + } + }); + return promise; + } + + public CompletableFuture getPosition() { + return currentPosition; + } + + /** + * Writes the given String appended with a newline separator + * and returns a CompletableFuture of the final file index + * after the completion of the corresponding write operation. + */ + public CompletableFuture writeLine(CharSequence str) { + return write(str + System.lineSeparator()); + } + + /** + * Writes the given String and returns a CompletableFuture of + * the final file index after the completion of the corresponding + * write operation. + */ + public CompletableFuture write(String str) { + return write(str.getBytes()); + } + + /** + * Writes the given byte array and returns a CompletableFuture of + * the final file index after the completion of the corresponding + * write operation. + */ + public CompletableFuture write(byte[] bytes) { + return write(wrap(bytes)); + } + + /** + * Writes the given byte buffer and returns a CompletableFuture of + * the final file index after the completion of the corresponding + * write operation. + */ + public CompletableFuture write(ByteBuffer bytes) { + /* + * Wee need to update currentPosition field to keep track. + * The currentPosition field is used on close() method, which chains + * a continuation to close the AsyncFileChannel. + */ + currentPosition = currentPosition.thenCompose(index -> { + CompletableFuture size = write(asyncFile, bytes, index); + return size.thenApply(length -> length + index); + }); + return currentPosition; + } + + /** + * Asynchronous close operation. + * Chains a continuation on CompletableFuture resulting from last write operation, + * which closes the AsyncFileChannel on completion. + * + * @throws IOException + */ + @Override + public void close() throws IOException { + if (asyncFile != null) { + currentPosition.whenComplete((res, ex) -> + AsyncFiles.closeAfc(asyncFile) + ); + } + } +} diff --git a/src/main/java/org/xbib/event/async/AsyncFiles.java b/src/main/java/org/xbib/event/async/AsyncFiles.java new file mode 100644 index 0000000..61b6bfe --- /dev/null +++ b/src/main/java/org/xbib/event/async/AsyncFiles.java @@ -0,0 +1,256 @@ +package org.xbib.event.async; + +import org.reactivestreams.Publisher; +import org.xbib.event.yield.AsyncQuery; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +import static java.nio.channels.AsynchronousFileChannel.open; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Asynchronous non-blocking read and write operations with a reactive based API. + * Read operations return a CompletableFuture with a single String or a Publisher + * of strings corresponding to lines. + * Write methods return a CompletableFuture with the final file index after the + * completion of corresponding write operation. + * These operations use an underlying AsynchronousFileChannel. + */ + +public class AsyncFiles { + + private AsyncFiles() { + } + + public static AsyncQuery asyncQuery(Path file) { + return new AsyncFileQuery(file); + } + + /** + * Reads the given file from the beginning using an AsyncFileChannel + * with a ByteBuffer of {@link AbstractAsyncFileReaderLines#BUFFER_SIZE BUFFER_SIZE} capacity. + */ + public static Publisher lines(String file) { + return lines(Paths.get(file)); + } + + /** + * Reads the given file from the beginning using an AsyncFileChannel + * with a ByteBuffer of {@link AbstractAsyncFileReaderLines#BUFFER_SIZE BUFFER_SIZE} capacity. + */ + public static Publisher lines(Path file) { + return lines(AbstractAsyncFileReaderLines.BUFFER_SIZE, file); + } + + /** + * Reads the given file from the beginning using + * an AsyncFileChannel with a ByteBuffer of + * the specified bufferSize capacity. + */ + public static Publisher lines(int bufferSize, String file) { + return lines(bufferSize, Paths.get(file)); + } + + /** + * Reads the given file from the beginning using + * an AsyncFileChannel with a ByteBuffer of + * the specified bufferSize capacity. + */ + public static Publisher lines(int bufferSize, Path file) { + return lines(bufferSize, file, StandardOpenOption.READ); + } + + /** + * Reads the given file from the beginning using + * an AsyncFileChannel with a ByteBuffer of + * the specified bufferSize capacity. + */ + public static Publisher lines(int bufferSize, Path file, StandardOpenOption... options) { + return sub -> { + AsyncFileReaderLines reader = null; + try { + AsynchronousFileChannel asyncFile = open(file, options); + reader = new AsyncFileReaderLines(sub, asyncFile, bufferSize); + } catch (IOException e) { + sub.onSubscribe(reader); + sub.onError(e); + return; + } + sub.onSubscribe(reader); + }; + } + + /** + * Reads the file from the beginning using an AsyncFileChannel + * with a ByteBuffer of {@link AbstractAsyncFileReaderLines#BUFFER_SIZE BUFFER_SIZE} capacity. + * It automatically closes the underlying AsyncFileChannel when read is complete. + */ + public static CompletableFuture readAll(String file) { + return readAll(Paths.get(file)); + } + + /** + * A callback based version of readAll(). + * Reads the file from the beginning using an AsyncFileChannel + * with a ByteBuffer of {@link AbstractAsyncFileReaderLines#BUFFER_SIZE BUFFER_SIZE} capacity. + * It automatically closes the underlying AsyncFileChannel when read is complete. + */ + public static void readAll(String file, BiConsumer callback) { + readAll(file, AbstractAsyncFileReaderLines.BUFFER_SIZE) + .whenComplete((data, err) -> { + if (err != null) callback.accept(err, null); + else callback.accept(null, data); + }); + } + + /** + * Reads the file from the beginning using + * an AsyncFileChannel with a ByteBuffer of + * the specified bufferSize capacity. + * It automatically closes the underlying AsyncFileChannel + * when read is complete. + */ + public static CompletableFuture readAll(String file, int bufferSize) { + return readAll(Paths.get(file), bufferSize); + } + + /** + * Reads the file from the beginning using an AsyncFileChannel + * with a ByteBuffer of {@link AbstractAsyncFileReaderLines#BUFFER_SIZE BUFFER_SIZE} capacity. + * It automatically closes the underlying AsyncFileChannel + * when read is complete. + */ + public static CompletableFuture readAll(Path file) { + return readAll(file, AbstractAsyncFileReaderLines.BUFFER_SIZE); + } + + /** + * Reads the file from the beginning using + * an AsyncFileChannel with a ByteBuffer of + * the specified bufferSize capacity. + * It automatically closes the underlying AsyncFileChannel + * when read is complete. + */ + public static CompletableFuture readAll(Path file, int bufferSize) { + return readAllBytes(file, bufferSize) + .thenApply(bytes -> new String(bytes, UTF_8)); + } + + /** + * Reads all bytes from the beginning of the file using an AsyncFileChannel + * with a ByteBuffer of {@link AbstractAsyncFileReaderLines#BUFFER_SIZE BUFFER_SIZE} capacity. + */ + public static CompletableFuture readAllBytes(Path file) { + return readAllBytes(file, AbstractAsyncFileReaderLines.BUFFER_SIZE); + } + + /** + * Reads all bytes from the beginning of the file + * using an AsyncFileChannel with a ByteBuffer of + * the specified bufferSize capacity. + */ + public static CompletableFuture readAllBytes(Path file, int bufferSize, StandardOpenOption... options) { + try { + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + AsynchronousFileChannel asyncFile = open(file, options); + CompletableFuture bytes = AsyncFileReaderBytes + .readAllBytes(asyncFile, buffer, 0, out) + .thenApply(position -> out.toByteArray()); + /* + * Deliberately chained in this way. + * Code smell: If closeAfc throws an Exception it will be lost! + */ + bytes.whenCompleteAsync((pos, ex) -> closeAfc(asyncFile)); + return bytes; + } catch (IOException e) { + return CompletableFuture.failedFuture(e); + } + } + + /** + * Writes bytes to a file. + * The options parameter specifies how the file is created or opened. + * All bytes in the byte array are written to the file. + * The method ensures that the file is closed when all bytes have been + * written (or an I/O error or other runtime exception is thrown). + * Returns a CompletableFuture with the final file index + * after the completion of the corresponding write operation. + * If an I/O error occurs then it may complete the resulting CompletableFuture + * exceptionally. + */ + public static CompletableFuture writeBytes(Path path, byte[] bytes) { + return writeBytes(path, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); + } + + /** + * Writes bytes to a file. + * The options parameter specifies how the file is created or opened. + * All bytes in the byte array are written to the file. + * The method ensures that the underlying {@code AsynchronousFileChannel} + * is closed when all bytes have been written (or an I/O error or any other + * runtime exception is thrown). + * Returns a {@code CompletableFuture} with the final file index + * after the completion of the corresponding write operation. + * If an I/O error occurs then it may complete the resulting CompletableFuture + * exceptionally. + */ + public static CompletableFuture writeBytes(Path path, byte[] bytes, StandardOpenOption... options) { + try (AsyncFileWriter writer = new AsyncFileWriter(path, options)) { + writer.write(bytes); + // The call to writer.close() is asynchronous and will chain + // a continuation to close the AsyncFileChannel only after completion. + return writer.getPosition(); + } catch (IOException e) { + return CompletableFuture.failedFuture(e); + } + } + + /** + * Write lines of text to a file. Each line is a char sequence and + * is written to the file in sequence with each line terminated by + * the platform's line separator, as defined by the system property + * line.separator. + * Returns a CompletableFuture with the final file index + * after the completion of the corresponding write operation. + */ + public static CompletableFuture write(Path path, Iterable lines) { + return write(path, lines, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); + } + + /** + * Write lines of text to a file. Each line is a char sequence and + * is written to the file in sequence with each line terminated by + * the platform's line separator, as defined by the system property + * line.separator. + * Returns a {@code CompletableFuture} with the final file index + * after the completion of the corresponding write operation. + */ + public static CompletableFuture write(Path path, Iterable lines, StandardOpenOption... options) { + try (AsyncFileWriter writer = new AsyncFileWriter(path, options)) { + lines.forEach(writer::writeLine); + // The call to writer.close() is asynchronous and will chain + // a continuation to close the AsyncFileChannel only after completion. + return writer.getPosition(); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + static void closeAfc(AsynchronousFileChannel asyncFile) { + try { + asyncFile.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/main/java/org/xbib/event/async/EmptySubscriber.java b/src/main/java/org/xbib/event/async/EmptySubscriber.java new file mode 100644 index 0000000..0d7c882 --- /dev/null +++ b/src/main/java/org/xbib/event/async/EmptySubscriber.java @@ -0,0 +1,28 @@ +package org.xbib.event.async; + +import org.reactivestreams.Subscription; + +/** + * For subscribers pipeline composition. + * This should the leaf of the pipeline. + * This avoids to check if next is null. + * + * @param + */ +public class EmptySubscriber implements org.reactivestreams.Subscriber { + @Override + public void onSubscribe(Subscription subscription) { + } + + @Override + public void onNext(T t) { + } + + @Override + public void onError(Throwable throwable) { + } + + @Override + public void onComplete() { + } +} diff --git a/src/main/java/org/xbib/event/async/SubscriberBuilder.java b/src/main/java/org/xbib/event/async/SubscriberBuilder.java new file mode 100644 index 0000000..a3753fe --- /dev/null +++ b/src/main/java/org/xbib/event/async/SubscriberBuilder.java @@ -0,0 +1,47 @@ +package org.xbib.event.async; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.util.function.Consumer; + +public class SubscriberBuilder implements Subscriber { + + private final Subscriber sub; + + public SubscriberBuilder(Subscriber sub) { + this.sub = sub; + } + + public SubscriberBuilder doOnSubscribe(Consumer cons) { + return new AddOnSubscribe<>(cons, this); + } + + public SubscriberBuilder doOnError(Consumer cons) { + return new AddOnError<>(cons, this); + } + + public SubscriberBuilder doOnComplete(Runnable action) { + return new AddOnComplete<>(action, this); + } + + @Override + public void onSubscribe(Subscription subscription) { + sub.onSubscribe(subscription); + } + + @Override + public void onNext(T item) { + sub.onNext(item); + } + + @Override + public void onError(Throwable throwable) { + sub.onError(throwable); + } + + @Override + public void onComplete() { + sub.onComplete(); + } +} diff --git a/src/main/java/org/xbib/event/async/Subscribers.java b/src/main/java/org/xbib/event/async/Subscribers.java new file mode 100644 index 0000000..c31bb11 --- /dev/null +++ b/src/main/java/org/xbib/event/async/Subscribers.java @@ -0,0 +1,13 @@ +package org.xbib.event.async; + +import java.util.function.Consumer; + +public class Subscribers { + + private Subscribers() { + } + + public static SubscriberBuilder doOnNext(Consumer cons) { + return new AddOnNext<>(cons); + } +} diff --git a/src/main/java/org/xbib/event/clock/ClockEvent.java b/src/main/java/org/xbib/event/clock/ClockEvent.java new file mode 100644 index 0000000..06e75f2 --- /dev/null +++ b/src/main/java/org/xbib/event/clock/ClockEvent.java @@ -0,0 +1,12 @@ +package org.xbib.event.clock; + +import org.xbib.event.Event; + +import java.time.Instant; + +public interface ClockEvent extends Event { + + void setInstant(Instant instant); + + Instant getInstant(); +} diff --git a/src/main/java/org/xbib/event/clock/ClockEventConsumer.java b/src/main/java/org/xbib/event/clock/ClockEventConsumer.java new file mode 100644 index 0000000..1b0d2a7 --- /dev/null +++ b/src/main/java/org/xbib/event/clock/ClockEventConsumer.java @@ -0,0 +1,18 @@ +package org.xbib.event.clock; + +import com.google.common.eventbus.AllowConcurrentEvents; +import com.google.common.eventbus.Subscribe; +import org.xbib.event.EventConsumer; + +import java.util.logging.Logger; + +public class ClockEventConsumer implements EventConsumer { + + private static final Logger logger = Logger.getLogger(ClockEventConsumer.class.getName()); + + @Subscribe + @AllowConcurrentEvents + void onEvent(ClockEvent event) { + logger.info("received demo clock event, instant = " + event.getInstant()); + } +} diff --git a/src/main/java/org/xbib/event/clock/ClockEventManager.java b/src/main/java/org/xbib/event/clock/ClockEventManager.java new file mode 100644 index 0000000..ea7eabe --- /dev/null +++ b/src/main/java/org/xbib/event/clock/ClockEventManager.java @@ -0,0 +1,61 @@ +package org.xbib.event.clock; + +import com.google.common.eventbus.EventBus; +import org.xbib.settings.Settings; +import org.xbib.time.schedule.CronExpression; +import org.xbib.time.schedule.CronSchedule; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.logging.Logger; + +public class ClockEventManager implements Closeable { + + private static final Logger logger = Logger.getLogger(ClockEventManager.class.getName()); + + private final CronSchedule cronSchedule; + + public ClockEventManager(Settings settings, + EventBus eventBus, + ClassLoader classLoader) { + ThreadFactory threadFactory = new ThreadFactory() { + int n = 1; + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "malva-clockevent-" + (n++)); + } + }; + ScheduledExecutorService executorService = + Executors.newScheduledThreadPool(settings.getAsInt("pool.size", 2), threadFactory); + this.cronSchedule = new CronSchedule<>(executorService); + for (Map.Entry cronjobs : settings.getGroups("clock").entrySet()) { + Settings entrySettings = cronjobs.getValue(); + if (entrySettings.getAsBoolean("enabled", true)) { + String entry = entrySettings.get("entry"); + if (entry != null) { + String className = entrySettings.get("class", ClockEvent.class.getName()); + try { + @SuppressWarnings("unchecked") + Class eventClass = (Class) classLoader.loadClass(className); + cronSchedule.add(className, CronExpression.parse(entry), new ClockEventService(eventBus, eventClass)); + logger.info("cron job " + cronjobs.getKey() + + " scheduled on " + entry + + ", event class " + className); + } catch (Exception e) { + logger.warning("unable to schedule cron job " + cronjobs.getKey() + ", reason " + e.getMessage()); + } + } + } + } + cronSchedule.start(); + } + + @Override + public void close() throws IOException { + cronSchedule.close(); + } +} diff --git a/src/main/java/org/xbib/event/clock/ClockEventService.java b/src/main/java/org/xbib/event/clock/ClockEventService.java new file mode 100644 index 0000000..a319cff --- /dev/null +++ b/src/main/java/org/xbib/event/clock/ClockEventService.java @@ -0,0 +1,37 @@ +package org.xbib.event.clock; + +import com.google.common.eventbus.EventBus; + +import java.time.Instant; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ClockEventService implements Callable { + + private static final Logger logger = Logger.getLogger(ClockEventService.class.getName()); + + private final EventBus eventBus; + + private final Class eventClass; + + public ClockEventService(EventBus eventBus, + Class eventClass) { + this.eventBus = eventBus; + this.eventClass = eventClass; + } + + @Override + public Integer call() throws Exception { + try { + logger.log(Level.FINE, "posting clock event " + eventClass.getName()); + ClockEvent clockEvent = eventClass.getDeclaredConstructor().newInstance(); + clockEvent.setInstant(Instant.now()); + eventBus.post(clockEvent); + return 0; + } catch (Throwable t) { + logger.log(Level.WARNING, t.getMessage(), t); + return 1; + } + } +} diff --git a/src/main/java/org/xbib/event/persistence/AbstractPersistenceStore.java b/src/main/java/org/xbib/event/persistence/AbstractPersistenceStore.java new file mode 100644 index 0000000..9d4ab6c --- /dev/null +++ b/src/main/java/org/xbib/event/persistence/AbstractPersistenceStore.java @@ -0,0 +1,80 @@ +package org.xbib.event.persistence; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("serial") +public class AbstractPersistenceStore extends LinkedHashMap + implements PersistenceStore { + + private final Durability durability; + + private final Codec> codec; + + private final String storeName; + + public AbstractPersistenceStore(Codec> codec, + Durability durability, + String storeName) { + super(); + this.codec = codec; + this.durability = durability; + this.storeName = storeName; + } + + @Override + public Durability getDurability() { + return durability; + } + + @Override + public Codec> getCodec() { + return codec; + } + + @Override + public void load() throws IOException { + clear(); + Map map = codec.read(storeName); + if (map != null) { + putAll(map); + } + } + + @Override + public void begin() { + // ignore + } + + @Override + public void commit() throws IOException { + codec.write(storeName, this); + } + + @Override + public void rollback() { + // ignore + } + + @SuppressWarnings("unchecked") + @Override + public void insert(String key, Object value) throws IOException { + putIfAbsent(key, new ArrayList<>()); + List list = (List) get(key); + list.add(value); + put(key, list); + commit(); + } + + @SuppressWarnings("unchecked") + void remove(String key, Object value) throws IOException { + putIfAbsent(key, new ArrayList<>()); + List list = (List) get(key); + list.remove(value); + put(key, list); + commit(); + } +} diff --git a/src/main/java/org/xbib/event/persistence/Codec.java b/src/main/java/org/xbib/event/persistence/Codec.java new file mode 100644 index 0000000..7ca09bf --- /dev/null +++ b/src/main/java/org/xbib/event/persistence/Codec.java @@ -0,0 +1,10 @@ +package org.xbib.event.persistence; + +import java.io.IOException; + +public interface Codec { + + D read(String key) throws IOException; + + void write(String key, D data) throws IOException; +} diff --git a/src/main/java/org/xbib/event/persistence/Durability.java b/src/main/java/org/xbib/event/persistence/Durability.java new file mode 100644 index 0000000..251bd0b --- /dev/null +++ b/src/main/java/org/xbib/event/persistence/Durability.java @@ -0,0 +1,6 @@ +package org.xbib.event.persistence; + +public enum Durability { + + VOLATILE, EXPLICIT, CONTAINER_LIFETIME, APPLICATION_LIFETIME, PERMANENT +} diff --git a/src/main/java/org/xbib/event/persistence/FileJsonCodec.java b/src/main/java/org/xbib/event/persistence/FileJsonCodec.java new file mode 100644 index 0000000..5ff993f --- /dev/null +++ b/src/main/java/org/xbib/event/persistence/FileJsonCodec.java @@ -0,0 +1,54 @@ +package org.xbib.event.persistence; + +import org.xbib.datastructures.json.tiny.Json; + +import java.io.IOException; +import java.io.Writer; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class FileJsonCodec implements Codec> { + + private final URI uri; + + private final ReentrantReadWriteLock readWriteLock; + + public FileJsonCodec(URI uri) { + this.uri = uri; + this.readWriteLock = new ReentrantReadWriteLock(); + } + + @Override + public Map read(String key) throws IOException { + try { + readWriteLock.readLock().lock(); + Path p = openOrCreate(key); + return Files.exists(p) ? Json.toMap(Files.newBufferedReader(p)) : null; + } finally { + readWriteLock.readLock().unlock(); + } + } + + @Override + public void write(String key, Map data) throws IOException { + try { + readWriteLock.writeLock().lock(); + Path p = openOrCreate(key); + try (Writer writer = Files.newBufferedWriter(p)) { + writer.write(Json.toString(data)); + } + } finally { + readWriteLock.writeLock().unlock(); + } + } + + private Path openOrCreate(String key) throws IOException { + Path path = Paths.get(uri); + Files.createDirectories(path); + return path.resolve(key); + } +} diff --git a/src/main/java/org/xbib/event/persistence/FilePersistenceStore.java b/src/main/java/org/xbib/event/persistence/FilePersistenceStore.java new file mode 100644 index 0000000..0295d06 --- /dev/null +++ b/src/main/java/org/xbib/event/persistence/FilePersistenceStore.java @@ -0,0 +1,24 @@ +package org.xbib.event.persistence; + +import org.xbib.settings.Settings; + +import java.net.URI; +import java.util.Map; + +@SuppressWarnings("serial") +public class FilePersistenceStore extends AbstractPersistenceStore { + + public FilePersistenceStore(Settings settings, String name) { + this(URI.create(settings.get("store.uri", "file:///var/tmp/malva/store")), + Durability.valueOf(settings.get("store.durability", "PERMANENT")), + settings.get("store.name", name)); + } + + public FilePersistenceStore(URI uri, Durability durability, String storeName) { + this(new FileJsonCodec(uri), durability, storeName); + } + + public FilePersistenceStore(Codec> codec, Durability durability, String storeName) { + super(codec, durability, storeName); + } +} diff --git a/src/main/java/org/xbib/event/persistence/PersistenceStore.java b/src/main/java/org/xbib/event/persistence/PersistenceStore.java new file mode 100644 index 0000000..f2b7533 --- /dev/null +++ b/src/main/java/org/xbib/event/persistence/PersistenceStore.java @@ -0,0 +1,21 @@ +package org.xbib.event.persistence; + +import java.io.IOException; +import java.util.Map; + +public interface PersistenceStore extends Map { + + Durability getDurability(); + + Codec> getCodec(); + + void insert(K k, V v) throws IOException; + + void load() throws IOException; + + void begin(); + + void commit() throws IOException; + + void rollback(); +} diff --git a/src/main/java/org/xbib/event/queue/QueueEvent.java b/src/main/java/org/xbib/event/queue/QueueEvent.java new file mode 100644 index 0000000..4a84797 --- /dev/null +++ b/src/main/java/org/xbib/event/queue/QueueEvent.java @@ -0,0 +1,38 @@ +package org.xbib.event.queue; + +import org.xbib.event.Event; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.time.Instant; +import java.util.Map; + +public class QueueEvent implements Event { + + private String key; + + private Map map; + + @Override + public void setKey(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } + + @Override + public void setMap(Map map) { + this.map = map; + } + + @Override + public Map getMap() { + return map; + } +} diff --git a/src/main/java/org/xbib/event/queue/path/simple/PathQueueEvent.java b/src/main/java/org/xbib/event/queue/path/simple/PathQueueEvent.java new file mode 100644 index 0000000..46b1e4e --- /dev/null +++ b/src/main/java/org/xbib/event/queue/path/simple/PathQueueEvent.java @@ -0,0 +1,61 @@ +package org.xbib.event.queue.path.simple; + +import org.xbib.event.queue.QueueEvent; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.time.Instant; + +public class PathQueueEvent extends QueueEvent { + + static final String INCOMING = "incoming"; + + static final String SUCCESS = "success"; + + static final String FAIL = "fail"; + + private Path path; + + private Path file; + + private String suffix; + + public void setFile(Path file) { + this.file = file; + } + + public Path getFile() { + return file; + } + + public void setPath(Path path) { + this.path = path; + } + + public Path getPath() { + return path; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + public String getSuffix() { + return suffix; + } + + public void success() throws IOException { + Files.setLastModifiedTime(file, FileTime.from(Instant.now())); + Files.move(file, path.resolve(SUCCESS).resolve(file.getFileName()).toAbsolutePath(), + StandardCopyOption.REPLACE_EXISTING); + } + + public void fail() throws IOException { + Files.setLastModifiedTime(file, FileTime.from(Instant.now())); + Files.move(file, path.resolve(FAIL).resolve(file.getFileName()).toAbsolutePath(), + StandardCopyOption.REPLACE_EXISTING); + } +} diff --git a/src/main/java/org/xbib/event/queue/path/simple/PathQueueEventManager.java b/src/main/java/org/xbib/event/queue/path/simple/PathQueueEventManager.java new file mode 100644 index 0000000..d1bea6a --- /dev/null +++ b/src/main/java/org/xbib/event/queue/path/simple/PathQueueEventManager.java @@ -0,0 +1,151 @@ +package org.xbib.event.queue.path.simple; + +import com.google.common.eventbus.EventBus; +import org.xbib.datastructures.api.TimeValue; +import org.xbib.datastructures.json.tiny.Json; +import org.xbib.settings.Settings; + +import java.io.IOException; +import java.io.Writer; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Instant; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; + +public class PathQueueEventManager { + + private static final Logger logger = Logger.getLogger(PathQueueEventManager.class.getName()); + + private final Settings settings; + + private final Path path; + + private final Map pathEventServiceMap; + + @SuppressWarnings("unchecked") + public PathQueueEventManager(Settings settings, + EventBus eventBus, + ClassLoader classLoader) { + this.settings = settings; + this.pathEventServiceMap = new LinkedHashMap<>(); + this.path = Paths.get(settings.get("path", "/var/tmp/default/queues")); + for (Map.Entry entry : settings.getGroups("queues").entrySet()) { + String name = entry.getKey(); + Settings definition = entry.getValue(); + String eventClassName = definition.get("class"); + if (eventClassName == null) { + throw new IllegalArgumentException("no path event class name given for path event manager at " + path); + } + try { + Class eventClass = (Class) classLoader.loadClass(eventClassName); + Path p = path.resolve(name); + if (!Files.exists(p)) { + logger.log(Level.INFO, "creating queue " + name + " at " + p); + Files.createDirectories(p); + } + for (String string : List.of(PathQueueEvent.INCOMING, PathQueueEvent.SUCCESS, PathQueueEvent.FAIL)) { + Path dir = p.resolve(string); + if (!Files.exists(dir)) { + logger.log(Level.INFO, "creating queue " + name + " dir = " + dir); + Files.createDirectories(dir); + } + } + if (!pathEventServiceMap.containsKey(p)) { + int maxFileSize = settings.getAsInt("maxfilesize", 10 * 1024 * 1024); // max. 10 MB + logger.log(Level.INFO, "creating path event service on " + p + " with class " + eventClassName + " max file size = " + maxFileSize); + PathQueueEventService pathEventService = new PathQueueEventService(eventBus, p, maxFileSize, eventClass); + pathEventServiceMap.put(p, pathEventService); + } + } catch (Exception e) { + logger.log(Level.WARNING, "unable to create path event service " + eventClassName + " reason " + e.getMessage()); + } + } + logger.log(Level.INFO, pathEventServiceMap.size() + " path event services initialized"); + } + + public boolean put(String queue, String key, Map map) throws IOException { + String keyFileName = key + ".json"; + Path queuePath = path.resolve(queue); + if (Files.exists(queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName)) || + Files.exists(queuePath.resolve(PathQueueEvent.SUCCESS).resolve(keyFileName))) { + logger.log(Level.WARNING, "key " + key + " already exists"); + return false; + } + Path file = queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName); + try (Writer writer = Files.newBufferedWriter(file)) { + writer.write(Json.toString(map)); + } + pathEventServiceMap.get(queuePath).postEvent(key, file); + purge(queue); + return true; + } + + public boolean put(String queue, String key, String value) throws IOException { + String keyFileName = key + ".txt"; + Path queuePath = path.resolve(queue); + if (Files.exists(queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName)) || + Files.exists(queuePath.resolve(PathQueueEvent.SUCCESS).resolve(keyFileName))) { + logger.log(Level.WARNING, "key " + key + " already exists"); + return false; + } + Path file = queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName); + try (Writer writer = Files.newBufferedWriter(file)) { + writer.write(value); + } + pathEventServiceMap.get(queuePath).postEvent(key, file); + purge(queue); + return true; + } + + public boolean exists(String queue, String key) { + String keyFileName = key + ".json"; + Path queuePath = path.resolve(queue); + return Files.exists(queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName)) || + Files.exists(queuePath.resolve(PathQueueEvent.SUCCESS).resolve(keyFileName)); + } + + public long sizeOfIncoming(String queue) throws IOException { + Path queuePath = path.resolve(queue); + Path incomingPath = queuePath.resolve(PathQueueEvent.INCOMING); + return sizeOf(incomingPath); + } + + public long sizeOfSuccess(String queue) throws IOException { + Path queuePath = path.resolve(queue); + Path successPath = queuePath.resolve(PathQueueEvent.SUCCESS); + return sizeOf(successPath); + } + + public static long sizeOf(Path path) throws IOException { + Stream stream = Files.find(path, 1, (p, basicFileAttributes) -> Files.isRegularFile(p)); + return stream.count(); + } + + public void purge(String queue) throws IOException { + List purgeQueues = Arrays.asList(settings.getAsArray("purge")); + if (purgeQueues.contains(queue)) { + TimeValue lifetime = settings.getAsTime("lifetime", TimeValue.timeValueHours(72)); + Path queuePath = path.resolve(queue); + Files.walkFileTree(queuePath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (Instant.now().minusMillis(attrs.lastModifiedTime().toMillis()).toEpochMilli() > lifetime.millis()) { + logger.log(Level.WARNING, "lifetime " + lifetime + " exceeded, deleting " + file); + Files.delete(file); + } + return FileVisitResult.CONTINUE; + } + }); + } + } +} diff --git a/src/main/java/org/xbib/event/queue/path/simple/PathQueueEventService.java b/src/main/java/org/xbib/event/queue/path/simple/PathQueueEventService.java new file mode 100644 index 0000000..bbf55c0 --- /dev/null +++ b/src/main/java/org/xbib/event/queue/path/simple/PathQueueEventService.java @@ -0,0 +1,102 @@ +package org.xbib.event.queue.path.simple; + +import com.google.common.eventbus.EventBus; +import org.xbib.datastructures.json.tiny.Json; + +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.xbib.event.queue.path.simple.PathQueueEvent.INCOMING; + +public class PathQueueEventService { + + private static final Logger logger = Logger.getLogger(PathQueueEventService.class.getName()); + + private final EventBus eventBus; + + private final Path path; + + private final int maxFileSize; + + private final Class pathEventClass; + + private int eventCount; + + public PathQueueEventService(EventBus eventBus, + Path path, + int maxFileSize, + Class pathEventClass) throws IOException { + this.eventBus = eventBus; + this.path = path; + this.maxFileSize = maxFileSize; + this.pathEventClass = pathEventClass; + drainIncoming(); + logger.log(Level.INFO, "at startup: watch service created for incoming files at " + path + + "max file size = " + maxFileSize); + } + + private void drainIncoming() throws IOException { + try (DirectoryStream directoryStream = Files.newDirectoryStream(path.resolve(INCOMING))) { + directoryStream.forEach(path -> { + if (Files.isRegularFile(path)) { + String key = path.getFileName().toString(); + logger.log(Level.INFO, "while draining found key=" + key + " path=" + path); + try { + postEvent(key, path); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + } + }); + } + } + + public void postEvent(String key, Path file) throws IOException { + String base = getBase(key); + String suffix = getSuffix(key); + PathQueueEvent event; + try { + event = pathEventClass.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + return; + } + event.setKey(base); + event.setFile(file); + event.setSuffix(suffix); + event.setMap(new LinkedHashMap<>()); + event.setPath(path); // remember directory for fail() and success() + long fileSize = Files.size(file); + if (fileSize < maxFileSize && Objects.equals(suffix, "json")) { + // we read only small JSON files + try (Reader reader = Files.newBufferedReader(file)) { + event.setMap(Json.toMap(reader)); + } catch (Exception e) { + logger.log(Level.WARNING, e.getMessage(), e); + } + logger.log(Level.INFO, "posting new event =" + event.getClass() + " " + event); + eventBus.post(event); + eventCount++; + } else { + logger.log(Level.SEVERE, "skipping post event because incoming file is too large, max file size = " + maxFileSize); + } + } + + public static String getBase(String name) { + int pos = name.lastIndexOf('.'); + return pos >= 0 ? name.substring(0, pos) : name; + } + + public static String getSuffix(String name) { + int pos = name.lastIndexOf('.'); + return pos >= 0 ? name.substring(pos + 1) : null; + } +} diff --git a/src/main/java/org/xbib/event/queue/path/watch/PathQueueEvent.java b/src/main/java/org/xbib/event/queue/path/watch/PathQueueEvent.java new file mode 100644 index 0000000..1de16ba --- /dev/null +++ b/src/main/java/org/xbib/event/queue/path/watch/PathQueueEvent.java @@ -0,0 +1,61 @@ +package org.xbib.event.queue.path.watch; + +import org.xbib.event.queue.QueueEvent; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.time.Instant; + +public class PathQueueEvent extends QueueEvent { + + static final String INCOMING = "incoming"; + + static final String SUCCESS = "success"; + + static final String FAIL = "fail"; + + private Path path; + + private Path file; + + private String suffix; + + public void setFile(Path file) { + this.file = file; + } + + public Path getFile() { + return file; + } + + public void setPath(Path path) { + this.path = path; + } + + public Path getPath() { + return path; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + public String getSuffix() { + return suffix; + } + + public void success() throws IOException { + Files.setLastModifiedTime(file, FileTime.from(Instant.now())); + Files.move(file, path.resolve(SUCCESS).resolve(file.getFileName()).toAbsolutePath(), + StandardCopyOption.REPLACE_EXISTING); + } + + public void fail() throws IOException { + Files.setLastModifiedTime(file, FileTime.from(Instant.now())); + Files.move(file, path.resolve(FAIL).resolve(file.getFileName()).toAbsolutePath(), + StandardCopyOption.REPLACE_EXISTING); + } +} diff --git a/src/main/java/org/xbib/event/queue/path/watch/PathQueueEventManager.java b/src/main/java/org/xbib/event/queue/path/watch/PathQueueEventManager.java new file mode 100644 index 0000000..2c1c699 --- /dev/null +++ b/src/main/java/org/xbib/event/queue/path/watch/PathQueueEventManager.java @@ -0,0 +1,168 @@ +package org.xbib.event.queue.path.watch; + +import com.google.common.eventbus.EventBus; +import org.xbib.datastructures.api.TimeValue; +import org.xbib.datastructures.json.tiny.Json; +import org.xbib.settings.Settings; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; + +public class PathQueueEventManager implements Closeable { + + private static final Logger logger = Logger.getLogger(PathQueueEventManager.class.getName()); + + private final Settings settings; + + private final Path path; + + private final List> futures; + + private final Map pathEventServiceMap; + + @SuppressWarnings("unchecked") + public PathQueueEventManager(Settings settings, + EventBus eventBus, + ExecutorService executorService, + ClassLoader classLoader) { + this.settings = settings; + this.futures = new ArrayList<>(); + this.pathEventServiceMap = new LinkedHashMap<>(); + this.path = Paths.get(settings.get("path", "/var/tmp/default/queues")); + for (Map.Entry entry : settings.getGroups("queues").entrySet()) { + String name = entry.getKey(); + Settings definition = entry.getValue(); + String eventClassName = definition.get("class"); + if (eventClassName == null) { + throw new IllegalArgumentException("no path event class name given for path event manager at " + path); + } + try { + Class eventClass = (Class) classLoader.loadClass(eventClassName); + Path p = path.resolve(name); + if (!Files.exists(p)) { + logger.log(Level.INFO, "creating queue " + name + " at " + p); + Files.createDirectories(p); + } + for (String string : List.of(PathQueueEvent.INCOMING, PathQueueEvent.SUCCESS, PathQueueEvent.FAIL)) { + Path dir = p.resolve(string); + if (!Files.exists(dir)) { + logger.log(Level.INFO, "creating queue " + name + " dir = " + dir); + Files.createDirectories(dir); + } + } + if (!pathEventServiceMap.containsKey(p)) { + int maxFileSize = settings.getAsInt("maxfilesize", 10 * 1024 * 1024); // max. 10 MB + logger.log(Level.INFO, "creating path event service on " + p + " with class " + eventClassName + " max file size = " + maxFileSize); + PathQueueEventService pathEventService = new PathQueueEventService(eventBus, p, maxFileSize, eventClass); + pathEventServiceMap.put(p, pathEventService); + futures.add(executorService.submit(pathEventService)); + } + } catch (Exception e) { + logger.log(Level.WARNING, "unable to create path event service " + eventClassName + " reason " + e.getMessage()); + } + } + logger.log(Level.INFO, pathEventServiceMap.size() + " path event services initialized"); + } + + public boolean put(String queue, String key, Map map) throws IOException { + String keyFileName = key + ".json"; + Path queuePath = path.resolve(queue); + if (Files.exists(queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName)) || + Files.exists(queuePath.resolve(PathQueueEvent.SUCCESS).resolve(keyFileName))) { + logger.log(Level.WARNING, "key " + key + " already exists"); + return false; + } + try (Writer writer = Files.newBufferedWriter(queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName))) { + writer.write(Json.toString(map)); + } + purge(queue); + return true; + } + + public boolean put(String queue, String key, String value) throws IOException { + String keyFileName = key + ".txt"; + Path queuePath = path.resolve(queue); + if (Files.exists(queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName)) || + Files.exists(queuePath.resolve(PathQueueEvent.SUCCESS).resolve(keyFileName))) { + logger.log(Level.WARNING, "key " + key + " already exists"); + return false; + } + try (Writer writer = Files.newBufferedWriter(queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName))) { + writer.write(value); + } + purge(queue); + return true; + } + + public boolean exists(String queue, String key) { + String keyFileName = key + ".json"; + Path queuePath = path.resolve(queue); + return Files.exists(queuePath.resolve(PathQueueEvent.INCOMING).resolve(keyFileName)) || + Files.exists(queuePath.resolve(PathQueueEvent.SUCCESS).resolve(keyFileName)); + } + + public long sizeOfIncoming(String queue) throws IOException { + Path queuePath = path.resolve(queue); + Path incomingPath = queuePath.resolve(PathQueueEvent.INCOMING); + return sizeOf(incomingPath); + } + + public long sizeOfSuccess(String queue) throws IOException { + Path queuePath = path.resolve(queue); + Path successPath = queuePath.resolve(PathQueueEvent.SUCCESS); + return sizeOf(successPath); + } + + public static long sizeOf(Path path) throws IOException { + Stream stream = Files.find(path, 1, (p, basicFileAttributes) -> Files.isRegularFile(p)); + return stream.count(); + } + + public void purge(String queue) throws IOException { + List purgeQueues = Arrays.asList(settings.getAsArray("purge")); + if (purgeQueues.contains(queue)) { + TimeValue lifetime = settings.getAsTime("lifetime", TimeValue.timeValueHours(72)); + Path queuePath = path.resolve(queue); + Files.walkFileTree(queuePath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (Instant.now().minusMillis(attrs.lastModifiedTime().toMillis()).toEpochMilli() > lifetime.millis()) { + logger.log(Level.WARNING, "lifetime " + lifetime + " exceeded, deleting " + file); + Files.delete(file); + } + return FileVisitResult.CONTINUE; + } + }); + } + } + + @Override + public void close() throws IOException { + logger.log(Level.INFO, "closing all path event services"); + for (PathQueueEventService service : pathEventServiceMap.values()) { + logger.log(Level.INFO, "closing path event service " + service); + service.close(); + } + for (Future future : futures) { + future.cancel(true); + } + } +} diff --git a/src/main/java/org/xbib/event/queue/path/watch/PathQueueEventService.java b/src/main/java/org/xbib/event/queue/path/watch/PathQueueEventService.java new file mode 100644 index 0000000..0e01884 --- /dev/null +++ b/src/main/java/org/xbib/event/queue/path/watch/PathQueueEventService.java @@ -0,0 +1,155 @@ +package org.xbib.event.queue.path.watch; + +import com.google.common.eventbus.EventBus; +import org.xbib.datastructures.json.tiny.Json; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.ClosedWatchServiceException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.xbib.event.queue.path.watch.PathQueueEvent.INCOMING; + +public class PathQueueEventService implements Callable, Closeable { + + private static final Logger logger = Logger.getLogger(PathQueueEventService.class.getName()); + + private final EventBus eventBus; + + private final Path path; + + private final int maxFileSize; + + private final Class pathEventClass; + + private final WatchService watchService; + + private int eventCount; + + private volatile boolean keepWatching; + + public PathQueueEventService(EventBus eventBus, + Path path, + int maxFileSize, + Class pathEventClass) throws IOException { + this.eventBus = eventBus; + this.path = path; + this.maxFileSize = maxFileSize; + this.pathEventClass = pathEventClass; + drainIncoming(); + this.watchService = path.getFileSystem().newWatchService(); + WatchEvent.Kind[] kinds = new WatchEvent.Kind[] { StandardWatchEventKinds.ENTRY_CREATE }; + WatchKey watchKey = path.resolve(INCOMING).register(watchService, kinds); + keepWatching = true; + logger.log(Level.INFO, "at startup: watch service created for incoming files at " + path + + "max file size = " + maxFileSize); + } + + @Override + public Integer call() { + try { + logger.log(Level.INFO, "watch service running on " + path.resolve(INCOMING)); + while (keepWatching && watchService != null) { + WatchKey watchKey = watchService.take(); + logger.log(Level.FINE, "received a watch key " + watchKey); + for (WatchEvent watchEvent : watchKey.pollEvents()) { + WatchEvent.Kind kind = watchEvent.kind(); + if (kind == StandardWatchEventKinds.OVERFLOW) { + continue; + } + // we sleep here a bit, to give time to the OS to complete file writing + Thread.sleep(1000L); + @SuppressWarnings("unchecked") + WatchEvent pathWatchEvent = (WatchEvent) watchEvent; + String watchEventContext = pathWatchEvent.context().toString(); + Path p = path.resolve(INCOMING).resolve(watchEventContext); + logger.log(Level.FINE, "watch event" + pathWatchEvent + " key=" + watchEventContext + " path=" + p); + postEvent(watchEventContext, p); + } + watchKey.reset(); + } + } catch (ClosedWatchServiceException e) { + logger.log(Level.FINE, "closed watch key"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.log(Level.FINE, "interrupted: " + e.getMessage()); + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + return eventCount; + } + + @Override + public void close() throws IOException { + keepWatching = false; + if (watchService != null) { + logger.log(Level.FINE, "closing watch service " + watchService); + watchService.close(); + } + } + + private void drainIncoming() throws IOException { + try (DirectoryStream directoryStream = Files.newDirectoryStream(path.resolve(INCOMING))) { + directoryStream.forEach(path -> { + if (Files.isRegularFile(path)) { + String key = path.getFileName().toString(); + logger.log(Level.INFO, "while draining found key=" + key + " path=" + path); + try { + postEvent(key, path); + } catch (IOException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + } + }); + } + } + + private void postEvent(String key, Path file) + throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { + String base = getBase(key); + String suffix = getSuffix(key); + PathQueueEvent event = pathEventClass.getConstructor().newInstance(); + event.setKey(base); + event.setFile(file); + event.setSuffix(suffix); + event.setMap(new LinkedHashMap<>()); + event.setPath(path); // remember directory for fail() and success() + long fileSize = Files.size(file); + if (fileSize < maxFileSize && Objects.equals(suffix, "json")) { + // we read only small JSON files + try (Reader reader = Files.newBufferedReader(file)) { + event.setMap(Json.toMap(reader)); + } catch (Exception e) { + logger.log(Level.WARNING, e.getMessage(), e); + } + logger.log(Level.INFO, "posting new event =" + event.getClass() + " " + event); + eventBus.post(event); + eventCount++; + } else { + logger.log(Level.SEVERE, "skipping post event because incoming file is too large, max file size = " + maxFileSize); + } + } + + public static String getBase(String name) { + int pos = name.lastIndexOf('.'); + return pos >= 0 ? name.substring(0, pos) : name; + } + + public static String getSuffix(String name) { + int pos = name.lastIndexOf('.'); + return pos >= 0 ? name.substring(pos + 1) : null; + } +} diff --git a/src/main/java/org/xbib/event/timer/TimerEvent.java b/src/main/java/org/xbib/event/timer/TimerEvent.java new file mode 100644 index 0000000..000ee85 --- /dev/null +++ b/src/main/java/org/xbib/event/timer/TimerEvent.java @@ -0,0 +1,12 @@ +package org.xbib.event.timer; + +import org.xbib.event.Event; + +import java.time.Instant; + +public interface TimerEvent extends Event { + + void setInstant(Instant instant); + + Instant getInstant(); +} diff --git a/src/main/java/org/xbib/event/timer/TimerEventManager.java b/src/main/java/org/xbib/event/timer/TimerEventManager.java new file mode 100644 index 0000000..9f43529 --- /dev/null +++ b/src/main/java/org/xbib/event/timer/TimerEventManager.java @@ -0,0 +1,90 @@ +package org.xbib.event.timer; + +import com.google.common.eventbus.EventBus; +import org.xbib.event.persistence.FilePersistenceStore; +import org.xbib.event.persistence.PersistenceStore; +import org.xbib.settings.Settings; +import org.xbib.time.chronic.Chronic; +import org.xbib.time.chronic.Span; + +import java.io.Closeable; +import java.io.IOException; +import java.text.ParseException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TimerEventManager implements Closeable { + + private static final Logger logger = Logger.getLogger(TimerEventManager.class.getName()); + + private final Map services; + + @SuppressWarnings("unchecked") + public TimerEventManager(Settings settings, + EventBus eventBus, + ClassLoader classLoader, + ZoneId zoneId) { + this.services = new LinkedHashMap<>(); + for (Map.Entry entry : settings.getGroups("timer").entrySet()) { + String name = entry.getKey(); + Settings timerSettings = entry.getValue(); + String className = timerSettings.get("class", TimerEvent.class.getName()); + try { + Class eventClass = (Class) classLoader.loadClass(className); + PersistenceStore persistenceStore = new FilePersistenceStore(timerSettings, name); + services.put(name, new TimerEventService(eventBus, name, eventClass, zoneId, persistenceStore)); + logger.log(Level.INFO, "timer " + name + " active for timer event class " + className); + } catch (Exception e) { + logger.log(Level.WARNING, "unable to activate timer " + name + ", reason " + e.getMessage()); + } + } + } + + public boolean put(String key, String timeSpec, Map map) throws ParseException, IOException { + if (services.containsKey(key)) { + Span span = Chronic.parse(timeSpec); + if (span != null) { + ZonedDateTime zonedDateTime = span.getBeginCalendar(); + services.get(key).schedule(zonedDateTime.toInstant(), map); + logger.log(Level.INFO, "scheduled to " + zonedDateTime); + } else { + logger.log(Level.INFO, "timer event key " + key + ": can not understand time spec " + timeSpec); + return false; + } + return true; + } else { + logger.log(Level.SEVERE, "unknown timer event key: " + key); + } + return false; + } + + public boolean put(String key, Instant instant, Map map) throws IOException { + if (services.containsKey(key)) { + services.get(key).schedule(instant, map); + return true; + } else { + logger.log(Level.SEVERE, "unknown timer event key: " + key); + } + return false; + } + + public void purge() { + for (Map.Entry entry : services.entrySet()) { + logger.log(Level.INFO, "purging timer " + entry.getKey()); + entry.getValue().purge(); + } + } + + @Override + public void close() throws IOException { + for (Map.Entry entry : services.entrySet()) { + logger.log(Level.INFO, "closing timer " + entry.getKey()); + entry.getValue().close(); + } + } +} diff --git a/src/main/java/org/xbib/event/timer/TimerEventService.java b/src/main/java/org/xbib/event/timer/TimerEventService.java new file mode 100644 index 0000000..2742f16 --- /dev/null +++ b/src/main/java/org/xbib/event/timer/TimerEventService.java @@ -0,0 +1,131 @@ +package org.xbib.event.timer; + +import com.google.common.eventbus.EventBus; +import org.xbib.event.persistence.PersistenceStore; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +class TimerEventService implements Closeable { + + private static final Logger logger = Logger.getLogger(TimerEventService.class.getName()); + + private final EventBus eventBus; + + private final Class eventClass; + + private final ZoneId zoneId; + + private final PersistenceStore persistenceStore; + + private final Timer timer; + + public TimerEventService(EventBus eventBus, + String name, + Class eventClass, + ZoneId zoneId, + PersistenceStore persistenceStore) throws IOException { + this.eventBus = eventBus; + this.eventClass = eventClass; + this.zoneId = zoneId; + this.persistenceStore = persistenceStore; + this.timer = new Timer(); + int tasknum = reschedule(); + logger.log(Level.INFO, "timer event service " + name + " loaded and rescheduled, " + tasknum + " timer tasks"); + } + + void schedule(Instant instant, Map task) throws IOException { + ZonedDateTime zonedDateTime = instant.atZone(zoneId); + task.put("scheduled", zonedDateTime.format(DateTimeFormatter.ISO_DATE_TIME)); + TimerEventTask timerEventTask = new TimerEventTask(task); + Date date = Date.from(instant); + timer.schedule(timerEventTask, date); + persistenceStore.insert("tasks", task); + logger.log(Level.INFO, "new task " + task + " added, scheduled at " + date); + } + + @SuppressWarnings("unchecked") + int reschedule() throws IOException { + logger.log(Level.INFO, "rescheduling timer events"); + persistenceStore.load(); + List> tasks = (List>) persistenceStore.getOrDefault("tasks", new ArrayList<>()); + persistenceStore.clear(); + persistenceStore.commit(); + for (Map task : tasks) { + ZonedDateTime scheduledDate = ZonedDateTime.parse((String) task.get("scheduled"), DateTimeFormatter.ISO_DATE_TIME); + if (scheduledDate.isBefore(ZonedDateTime.now())) { + logger.log(Level.WARNING, "scheduled timer task " + task + " date already passed"); + } else { + schedule(scheduledDate.toInstant(), task); + } + } + tasks = (List>) persistenceStore.getOrDefault("tasks", new ArrayList<>()); + logger.log(Level.INFO, "rescheduling timer events complete: " + tasks.size() + " events"); + return tasks.size(); + } + + @Override + public void close() throws IOException { + timer.cancel(); + persistenceStore.commit(); + } + + void purge() { + timer.purge(); + } + + public class TimerEventTask extends TimerTask { + + private final Map map; + + public TimerEventTask(Map map) { + this.map = map; + } + + @Override + public void run() { + TimerEvent timerEvent; + try { + timerEvent = eventClass.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + return; + } + timerEvent.setInstant(Instant.now()); + timerEvent.setMap(map); + logger.log(Level.FINE, "posting timer event " + timerEvent.getClass().getName() + " map = " + map); + eventBus.post(timerEvent); + persistenceStore.remove("tasks", this); + logger.log(Level.FINE, "persistence after remove: " + persistenceStore.get("tasks")); + } + + @Override + public String toString() { + return map.toString(); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public boolean equals(Object object) { + return object instanceof TimerEventTask && Objects.equals(map, object); + } + } +} diff --git a/src/main/java/org/xbib/event/yield/Advancer.java b/src/main/java/org/xbib/event/yield/Advancer.java new file mode 100644 index 0000000..352871f --- /dev/null +++ b/src/main/java/org/xbib/event/yield/Advancer.java @@ -0,0 +1,19 @@ +package org.xbib.event.yield; + +/** + * Sequential traverser with internal and individually step approach. + */ +public interface Advancer { + /** + * An Advancer object without elements. + */ + static Advancer empty() { + return action -> false; + } + + /** + * If a remaining element exists, yields that element through + * the given action. + */ + boolean tryAdvance(Yield yield); +} diff --git a/src/main/java/org/xbib/event/yield/AsyncQuery.java b/src/main/java/org/xbib/event/yield/AsyncQuery.java new file mode 100644 index 0000000..83cb41b --- /dev/null +++ b/src/main/java/org/xbib/event/yield/AsyncQuery.java @@ -0,0 +1,121 @@ +package org.xbib.event.yield; + +import org.xbib.event.yield.async.AsyncQueryDistinct; +import org.xbib.event.yield.async.AsyncQueryFilter; +import org.xbib.event.yield.async.AsyncQueryFlatMapConcat; +import org.xbib.event.yield.async.AsyncQueryFlatMapMerge; +import org.xbib.event.yield.async.AsyncQueryFork; +import org.xbib.event.yield.async.AsyncQueryMap; +import org.xbib.event.yield.async.AsyncQueryOf; +import org.xbib.event.yield.async.AsyncQueryOfIterator; +import org.xbib.event.yield.async.AsyncQueryOnNext; +import org.xbib.event.yield.async.AsyncQuerySkip; +import org.xbib.event.yield.async.AsyncQueryTakeWhile; + +import java.util.Iterator; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * An asynchronous sequence of elements supporting sequential operations. + * Query operations are composed into a pipeline to perform computation. + */ +public abstract class AsyncQuery implements AsyncTraverser { + + /** + * Returns an asynchronous sequential ordered query whose elements + * are the specified values in data parameter. + */ + @SuppressWarnings("unchecked") + public static AsyncQuery of(U... data) { + return new AsyncQueryOf<>(data); + } + + /** + * Returns an asynchronous sequential ordered query whose elements + * are the specified values in the Iterator parameter. + */ + public static AsyncQuery of(Iterator iter) { + return new AsyncQueryOfIterator<>(iter); + } + + /** + * Returns an asynchronous sequential ordered query whose elements + * are the specified values in data parameter running on thread pool. + */ + @SuppressWarnings("unchecked") + public static AsyncQuery fork(U... data) { + return new AsyncQueryFork<>(data); + } + + /** + * Returns a new asynchronous query emitting the same items of this query, + * additionally performing the provided action on each element as elements are consumed + * from the resulting query. + */ + public final AsyncQuery onNext(BiConsumer action) { + return new AsyncQueryOnNext<>(this, action); + } + + /** + * Returns a new asynchronous query consisting of the remaining elements of + * this query after discarding the first {@code n} elements of the query. + */ + public final AsyncQuery skip(int n) { + return new AsyncQuerySkip<>(this, n); + } + + /** + * Returns an asynchronous query consisting of the elements of this query that match + * the given predicate. + */ + public final AsyncQuery filter(Predicate p) { + return new AsyncQueryFilter<>(this, p); + } + + /** + * Returns an asynchronous query consisting of the results of applying the given + * function to the elements of this query. + */ + public final AsyncQuery map(Function mapper) { + return new AsyncQueryMap<>(this, mapper); + } + + /** + * Returns a query consisting of the distinct elements (according to + * {@link Object#equals(Object)}) of this query. + */ + public final AsyncQuery distinct() { + return new AsyncQueryDistinct<>(this); + } + + /** + * Returns a query consisting of the longest prefix of elements taken from + * this query that match the given predicate. + */ + public final AsyncQuery takeWhile(Predicate predicate) { + return new AsyncQueryTakeWhile<>(this, predicate); + } + + /** + * Returns an asynchronous query consisting of the results of replacing each element of + * this query with the contents of a mapped query produced by applying + * the provided mapping function to each element. + * It waits for the inner flow to complete before starting to collect the next one. + */ + public final AsyncQuery flatMapConcat(Function> mapper) { + return new AsyncQueryFlatMapConcat<>(this, mapper); + } + + public final AsyncQuery flatMapMerge(Function> mapper) { + return new AsyncQueryFlatMapMerge<>(this, mapper); + } + + public final void blockingSubscribe() { + this + .subscribe((item, err) -> { + }) + .join(); // In both previous cases cf will raise an exception. + } +} diff --git a/src/main/java/org/xbib/event/yield/AsyncTraverser.java b/src/main/java/org/xbib/event/yield/AsyncTraverser.java new file mode 100644 index 0000000..383b979 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/AsyncTraverser.java @@ -0,0 +1,23 @@ +package org.xbib.event.yield; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +/** + * Asynchronous traversal. + * We use a traverse method as its first choice to + * implement AsyncQuery operations. + * This is a special kind of traversal that disallows individually access. + */ +public interface AsyncTraverser { + /** + * Yields elements sequentially until all elements have been + * processed or an exception is thrown. + * The given consumer is invoked with the result (or null if none) + * and the exception (or null if none). + * + * @return A CompletableFuture to signal finish to enable cancellation + * through its cancel() method. + */ + CompletableFuture subscribe(BiConsumer cons); +} diff --git a/src/main/java/org/xbib/event/yield/Query.java b/src/main/java/org/xbib/event/yield/Query.java new file mode 100644 index 0000000..3194017 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/Query.java @@ -0,0 +1,535 @@ +package org.xbib.event.yield; + +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.boxes.Box; +import org.xbib.event.yield.ops.Concat; +import org.xbib.event.yield.ops.Distinct; +import org.xbib.event.yield.ops.DropWhile; +import org.xbib.event.yield.ops.Filter; +import org.xbib.event.yield.ops.FlatMap; +import org.xbib.event.yield.ops.FromArray; +import org.xbib.event.yield.ops.FromList; +import org.xbib.event.yield.ops.FromStream; +import org.xbib.event.yield.ops.Generate; +import org.xbib.event.yield.ops.Iterate; +import org.xbib.event.yield.ops.Limit; +import org.xbib.event.yield.ops.Mapping; +import org.xbib.event.yield.ops.Peek; +import org.xbib.event.yield.ops.Skip; +import org.xbib.event.yield.ops.TakeWhile; +import org.xbib.event.yield.ops.Zip; +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators.AbstractSpliterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * A sequence of elements supporting sequential operations. + * Query operations are composed into a pipeline to perform + * computation. + */ +public class Query { + + private final Advancer adv; + private final Traverser trav; + + public Query(Advancer adv, Traverser trav) { + this.adv = adv; + this.trav = trav; + } + + /** + * Returns a sequential ordered query whose elements + * are the specified values in data parameter. + */ + @SuppressWarnings("unchecked") + public static Query of(U... data) { + FromArray adv = new FromArray<>(data); + return new Query<>(adv, adv); + } + + /** + * Returns a sequential ordered query with elements + * from the provided List data. + */ + public static Query fromList(List data) { + FromList adv = new FromList<>(data); + return new Query<>(adv, adv); + } + + /** + * Returns a sequential ordered query with elements + * from the provided stream data. + */ + public static Query fromStream(Stream data) { + FromStream adv = new FromStream<>(data); + return new Query<>(adv, adv); + } + + /** + * Returns an infinite sequential ordered {@code Query} produced by iterative + * application of a function {@code f} to an initial element {@code seed}, + * producing a {@code Query} consisting of {@code seed}, {@code f(seed)}, + * {@code f(f(seed))}, etc. + */ + public static Query iterate(U seed, UnaryOperator f) { + Iterate iter = new Iterate<>(seed, f); + return new Query<>(iter, iter); + } + + /** + * Returns an infinite sequential unordered {@code Query} + * where each element is generated by the provided Supplier. + */ + public static Query generate(Supplier s) { + Generate gen = new Generate<>(s); + return new Query<>(gen, gen); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + public final void traverse(Yield yield) { + this.trav.traverse(yield); + } + + /** + * If a remaining element exists, yields that element through + * the given action. + */ + public boolean tryAdvance(Yield action) { + return this.adv.tryAdvance(action); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or the traversal + * exited normally through the invocation of yield.bye(). + */ + public final void shortCircuit(Yield yield) { + try { + this.trav.traverse(yield); + } catch (TraversableFinishError e) { + /* Proceed */ + } + } + + /** + * Returns a query consisting of the results of applying the given + * function to the elements of this query. + */ + public final Query map(Function mapper) { + Mapping map = new Mapping<>(this, mapper); + return new Query<>(map, map); + } + + /** + * Applies a specified function to the corresponding elements of two + * sequences, producing a sequence of the results. + */ + public final Query zip(Query other, BiFunction zipper) { + Zip zip = new Zip<>(this, other, zipper); + return new Query<>(zip, zip); + } + + /** + * Returns a {@link IntQuery} with the elements of this {@code Query} mapped by + * a {@link ToIntFunction} + * + * @param mapper ToIntFunction used to map elements of this {@code Query} to int + */ + public final IntQuery mapToInt(ToIntFunction mapper) { + return new IntQuery(IntAdvancer.from(adv, mapper), IntTraverser.from(trav, mapper)); + } + + /** + * Returns a {@link LongQuery} with the elements of this {@code Query} mapped by + * a {@link ToLongFunction} + * + * @param mapper ToLongFunction used to map elements of this {@code Query} to long + */ + public final LongQuery mapToLong(ToLongFunction mapper) { + return new LongQuery(LongAdvancer.from(adv, mapper), LongTraverser.from(trav, mapper)); + } + + /** + * Returns a {@link DoubleQuery} with the elements of this {@code Query} mapped by + * a {@link ToDoubleFunction} + * + * @param mapper ToLongFunction used to map elements of this {@code Query} to double + */ + public final DoubleQuery mapToDouble(ToDoubleFunction mapper) { + return new DoubleQuery(DoubleAdvancer.from(adv, mapper), DoubleTraverser.from(trav, mapper)); + } + + /** + * Returns a query consisting of the elements of this query that match + * the given predicate. + */ + public final Query filter(Predicate p) { + Filter filter = new Filter<>(this, p); + return new Query<>(filter, filter); + } + + /** + * Returns a query consisting of the remaining elements of this query + * after discarding the first {@code n} elements of the query. + */ + public final Query skip(int n) { + Skip skip = new Skip<>(this, n); + return new Query<>(skip, skip); + } + + /** + * Returns a query consisting of the elements of this query, truncated + * to be no longer than {@code n} in length. + */ + public final Query limit(int n) { + Limit limit = new Limit<>(this, n); + return new Query<>(limit, limit); + } + + /** + * Returns a query consisting of the distinct elements (according to + * {@link Object#equals(Object)}) of this query. + */ + public final Query distinct() { + Distinct dis = new Distinct<>(this); + return new Query<>(dis, dis); + } + + /** + * Returns a query consisting of the results of replacing each element of + * this query with the contents of a mapped query produced by applying + * the provided mapping function to each element. + */ + public final Query flatMap(Function> mapper) { + FlatMap map = new FlatMap<>(this, mapper); + return new Query<>(map, map); + } + + /** + * Returns a query consisting of the elements of this query, additionally + * performing the provided action on each element as elements are consumed + * from the resulting query. + */ + public final Query peek(Consumer action) { + Peek peek = new Peek<>(this, action); + return new Query<>(peek, peek); + } + + /** + * Returns a query consisting of the longest prefix of elements taken from + * this query that match the given predicate. + */ + public final Query takeWhile(Predicate predicate) { + TakeWhile take = new TakeWhile<>(this, predicate); + return new Query<>(take, take); + } + + /** + * The {@code then} operator lets you encapsulate a piece of an operator + * chain into a function. + * That function {@code next} is applied to this query to produce a new + * {@code Traverser} object that is encapsulated in the resulting query. + * On the other hand, the {@code nextAdv} is applied to this query to produce a new + * {@code Advancer} object that is encapsulated in the resulting query. + */ + public final Query then(Function, Advancer> nextAdv, Function, Traverser> next) { + return new Query<>(nextAdv.apply(this), next.apply(this)); + } + + /** + * The {@code then} operator lets you encapsulate a piece of an operator + * chain into a function. + * That function {@code next} is applied to this query to produce a new + * {@code Traverser} object that is encapsulated in the resulting query. + */ + public final Query then(Function, Traverser> next) { + Advancer nextAdv = item -> { + throw new UnsupportedOperationException( + "Missing tryAdvance() implementation! Use the overloaded then() providing both Advancer and Traverser!"); + }; + return new Query<>(nextAdv, next.apply(this)); + } + + /** + * Returns a list containing the elements of this query. + */ + public final List toList() { + List data = new ArrayList<>(); + this.traverse(data::add); + return data; + } + + /** + * Returns an array containing the elements of this query. + */ + public final Object[] toArray() { + return this.toArray(Object[]::new); + } + + public final Stream toStream() { + Spliterator iter = new AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { + @Override + public boolean tryAdvance(Consumer action) { + return adv.tryAdvance(action::accept); + } + + @Override + public void forEachRemaining(Consumer action) { + trav.traverse(action::accept); + } + }; + return StreamSupport.stream(iter, false); + } + + /** + * Returns an {@link Optional} describing the first element of this query, + * or an empty {@code Optional} if this query is empty. + */ + public final Optional findFirst() { + Box box = new Box<>(); + this.tryAdvance(box::turnPresent); + return box.isPresent() + ? Optional.of(box.getValue()) + : Optional.empty(); + } + + /** + * Returns the maximum element of this query according to the provided + * {@code Comparator}. This is a special case of a reduction. + */ + public final Optional max(Comparator cmp) { + class BoxMax extends Box implements Yield { + @Override + public final void ret(T item) { + if (!isPresent()) turnPresent(item); + else if (cmp.compare(item, value) > 0) value = item; + } + } + BoxMax b = new BoxMax(); + this.traverse(b); + return b.isPresent() ? Optional.of(b.getValue()) : Optional.empty(); + } + + /** + * Returns whether any elements of this query match the provided + * predicate. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code false} is returned and the predicate is not evaluated. + */ + public final boolean anyMatch(Predicate p) { + BoolBox found = new BoolBox(); + shortCircuit(item -> { + if (p.test(item)) { + found.set(); + Yield.bye(); + } + }); + return found.isTrue(); + } + + /** + * Returns whether all elements of this query match the provided + * predicate. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code true} is returned and the predicate is not evaluated. + */ + public final boolean allMatch(Predicate p) { + BoolBox succeed = new BoolBox(true); + shortCircuit(item -> { + if (!p.test(item)) { + succeed.set(false); + Yield.bye(); + } + }); + return succeed.isTrue(); + } + + /** + * Returns the count of elements in this query. + */ + public final long count() { + class Counter implements Yield { + long n = 0; + + @Override + public void ret(T item) { + ++n; + } + } + Counter c = new Counter(); + this.traverse(c); + return c.n; + } + + /** + * Returns an {@link Optional} with the resulting reduction of the elements of this {@code Query}, + * if a reduction can be made, using the provided accumulator. + */ + public Optional reduce(BinaryOperator accumulator) { + Box box = new Box<>(); + if (this.tryAdvance(box::setValue)) { + return Optional.of(this.reduce(box.getValue(), accumulator)); + } else { + return Optional.empty(); + } + } + + /** + * Returns the result of the reduction of the elements of this query, + * using the provided identity value and accumulator. + */ + public T reduce(T identity, BinaryOperator accumulator) { + class BoxAccumulator extends Box implements Yield { + public BoxAccumulator(T identity) { + super(identity); + } + + @Override + public final void ret(T item) { + this.value = accumulator.apply(value, item); + } + } + BoxAccumulator box = new BoxAccumulator(identity); + this.traverse(box); + return box.getValue(); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + public final void forEach(Yield yield) { + this.traverse(yield); + } + + /** + * Returns a {@link Set} containing the elements of this query. + */ + public final Set toSet() { + Set data = new HashSet<>(); + this.traverse(data::add); + return data; + } + + /** + * Returns an array containing the elements of this query. + */ + public final U[] toArray(IntFunction generator) { + return this.toList().toArray(generator); + } + + /** + * Returns the concatenation of the input elements into a String, in encounter order. + */ + public final String join() { + return this.map(String::valueOf) + .collect(StringBuilder::new, StringBuilder::append) + .toString(); + } + + /** + * Returns an {@link Optional} describing any element of this query, + * or an empty {@code Optional} if this query is empty. + */ + public final Optional findAny() { + return this.findFirst(); + } + + /** + * Returns the minimum element of this query according to the provided + * {@code Comparator}. This is a special case of a reduction. + */ + public final Optional min(Comparator cmp) { + return this.max((a, b) -> cmp.compare(a, b) * -1); + } + + /** + * Returns whether no elements of this query match the provided + * predicate. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code true} is returned and the predicate is not evaluated. + */ + public final boolean noneMatch(Predicate p) { + return !this.anyMatch(p); + } + + /** + * Performs a mutable reduction operation on the elements of this {@code Query}. + * A mutable reduction is one in which the reduced value is a mutable result container, such as an ArrayList, + * and elements are incorporated by updating the state of the result rather than by replacing the result. + */ + public R collect(Supplier supplier, BiConsumer accumulator) { + R result = supplier.get(); + this.traverse(elem -> accumulator.accept(result, elem)); + return result; + } + + /** + * Creates a concatenated {@code Query} in which the elements are + * all the elements of this {@code Query} followed by all the + * elements of the other {@code Query}. + */ + public final Query concat(Query other) { + Concat con = new Concat<>(this, other); + return new Query<>(con, con); + } + + /** + * Returns a {@code Query} consisting of the elements of this {@code Query}, + * sorted according to the provided Comparator. + *

+ * This is a stateful intermediate operation. + */ + @SuppressWarnings("unchecked") + public final Query sorted(Comparator comparator) { + T[] state = (T[]) this.toArray(); + Arrays.sort(state, comparator); + FromArray sorted = new FromArray<>(state); + return new Query<>(sorted, sorted); + } + + /** + * Returns a {@code Query} consisting of the remaining elements of this query + * after discarding the first sequence of elements that match the given Predicate. + */ + public final Query dropWhile(Predicate predicate) { + DropWhile drop = new DropWhile<>(this, predicate); + return new Query<>(drop, drop); + } + +} diff --git a/src/main/java/org/xbib/event/yield/TraversableFinishError.java b/src/main/java/org/xbib/event/yield/TraversableFinishError.java new file mode 100644 index 0000000..e9d4fe1 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/TraversableFinishError.java @@ -0,0 +1,11 @@ +package org.xbib.event.yield; + +@SuppressWarnings("serial") +public class TraversableFinishError extends Error { + + public static final TraversableFinishError finishTraversal = new TraversableFinishError(); + + private TraversableFinishError() { + super("Auxiliary exception finishes traversal!", null, false, false); + } +} diff --git a/src/main/java/org/xbib/event/yield/Traverser.java b/src/main/java/org/xbib/event/yield/Traverser.java new file mode 100644 index 0000000..7e71f0c --- /dev/null +++ b/src/main/java/org/xbib/event/yield/Traverser.java @@ -0,0 +1,24 @@ +package org.xbib.event.yield; + +/** + * Bulk traversal. + * We uses traverse method as its first choice to + * implement Query operations. + * This is a special kind of traversal that disallows individually access. + */ +public interface Traverser { + /** + * A Traverser object without elements. + */ + static Traverser empty() { + return action -> { + }; + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + void traverse(Yield yield); +} diff --git a/src/main/java/org/xbib/event/yield/Yield.java b/src/main/java/org/xbib/event/yield/Yield.java new file mode 100644 index 0000000..f99e9f1 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/Yield.java @@ -0,0 +1,16 @@ +package org.xbib.event.yield; + +/** + * Equivalent to {@code Consumer} with a yield semantics. + */ +@FunctionalInterface +public interface Yield { + /** + * Auxiliary function for traversal short circuit. + */ + static void bye() { + throw TraversableFinishError.finishTraversal; + } + + void ret(T item); +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryDistinct.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryDistinct.java new file mode 100644 index 0000000..405ab1b --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryDistinct.java @@ -0,0 +1,28 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; + +import java.util.HashSet; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +public class AsyncQueryDistinct extends AsyncQuery { + + private final AsyncQuery upstream; + + public AsyncQueryDistinct(AsyncQuery upstream) { + this.upstream = upstream; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + final HashSet mem = new HashSet<>(); + return upstream.subscribe((item, err) -> { + if (err != null) { + cons.accept(null, err); + return; + } + if (mem.add(item)) cons.accept(item, null); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryFilter.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryFilter.java new file mode 100644 index 0000000..b76285e --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryFilter.java @@ -0,0 +1,28 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.Predicate; + +public class AsyncQueryFilter extends AsyncQuery { + private final AsyncQuery upstream; + private final Predicate p; + + public AsyncQueryFilter(AsyncQuery upstream, Predicate p) { + this.upstream = upstream; + this.p = p; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + return upstream.subscribe((item, err) -> { + if (err != null) { + cons.accept(null, err); + return; + } + if (p.test(item)) cons.accept(item, null); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryFlatMapConcat.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryFlatMapConcat.java new file mode 100644 index 0000000..9d64164 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryFlatMapConcat.java @@ -0,0 +1,32 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class AsyncQueryFlatMapConcat extends AsyncQuery { + + private final AsyncQuery upstream; + + private final Function> mapper; + + public AsyncQueryFlatMapConcat(AsyncQuery upstream, Function> mapper) { + this.upstream = upstream; + this.mapper = mapper; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + return upstream.subscribe((item, err) -> { + if (err != null) { + cons.accept(null, err); + return; + } + mapper.apply(item) + .subscribe(cons) + .join(); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryFlatMapMerge.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryFlatMapMerge.java new file mode 100644 index 0000000..32af2c2 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryFlatMapMerge.java @@ -0,0 +1,38 @@ +package org.xbib.event.yield.async; + + +import org.xbib.event.yield.AsyncQuery; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class AsyncQueryFlatMapMerge extends AsyncQuery { + + private final AsyncQuery upstream; + + private final Function> mapper; + + public AsyncQueryFlatMapMerge(AsyncQuery upstream, Function> mapper) { + this.upstream = upstream; + this.mapper = mapper; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + List> cfs = new ArrayList<>(); + return upstream + .subscribe((item, err) -> { + if (err != null) { + cons.accept(null, err); + return; + } + cfs.add(mapper + .apply(item) + .subscribe(cons)); + }) + .thenCompose(ignore -> CompletableFuture.allOf(cfs.toArray(new CompletableFuture[0]))); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryFork.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryFork.java new file mode 100644 index 0000000..791f559 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryFork.java @@ -0,0 +1,25 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; +import org.xbib.event.yield.Query; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +import static java.util.concurrent.CompletableFuture.runAsync; + +public class AsyncQueryFork extends AsyncQuery { + + private final U[] data; + + public AsyncQueryFork(U[] data) { + this.data = data; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + return runAsync(() -> Query + .of(data) + .traverse(item -> cons.accept(item, null))); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryMap.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryMap.java new file mode 100644 index 0000000..4491cd7 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryMap.java @@ -0,0 +1,30 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class AsyncQueryMap extends AsyncQuery { + + private final AsyncQuery upstream; + + private final Function mapper; + + public AsyncQueryMap(AsyncQuery upstream, Function mapper) { + this.upstream = upstream; + this.mapper = mapper; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + return upstream.subscribe((item, err) -> { + if (err != null) { + cons.accept(null, err); + return; + } + cons.accept(mapper.apply(item), null); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryOf.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryOf.java new file mode 100644 index 0000000..c03e2fb --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryOf.java @@ -0,0 +1,22 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; +import org.xbib.event.yield.Query; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +public class AsyncQueryOf extends AsyncQuery { + + private final U[] data; + + public AsyncQueryOf(U[] data) { + this.data = data; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + Query.of(data).traverse(item -> cons.accept(item, null)); + return CompletableFuture.completedFuture(null); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryOfIterator.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryOfIterator.java new file mode 100644 index 0000000..5b74558 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryOfIterator.java @@ -0,0 +1,24 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; + +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +public class AsyncQueryOfIterator extends AsyncQuery { + + private final Iterator iter; + + public AsyncQueryOfIterator(Iterator iter) { + this.iter = iter; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + while (iter.hasNext()) { + cons.accept(iter.next(), null); + } + return CompletableFuture.completedFuture(null); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryOnNext.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryOnNext.java new file mode 100644 index 0000000..ab0ec70 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryOnNext.java @@ -0,0 +1,26 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +public class AsyncQueryOnNext extends AsyncQuery { + + private final AsyncQuery upstream; + + private final BiConsumer action; + + public AsyncQueryOnNext(AsyncQuery upstream, BiConsumer action) { + this.upstream = upstream; + this.action = action; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + return upstream.subscribe((item, err) -> { + action.accept(item, err); + cons.accept(item, err); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQuerySkip.java b/src/main/java/org/xbib/event/yield/async/AsyncQuerySkip.java new file mode 100644 index 0000000..7f092eb --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQuerySkip.java @@ -0,0 +1,33 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; +import org.xbib.event.yield.AsyncTraverser; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +public class AsyncQuerySkip extends AsyncQuery { + + private final AsyncTraverser upstream; + + private final int skip; + + private int count = 0; + + public AsyncQuerySkip(AsyncTraverser upstream, int skip) { + this.upstream = upstream; + this.skip = skip; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + return upstream.subscribe((item, err) -> { + if (err != null) { + cons.accept(null, err); + return; + } + if (count >= skip) cons.accept(item, err); + else count++; + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/async/AsyncQueryTakeWhile.java b/src/main/java/org/xbib/event/yield/async/AsyncQueryTakeWhile.java new file mode 100644 index 0000000..439dc0f --- /dev/null +++ b/src/main/java/org/xbib/event/yield/async/AsyncQueryTakeWhile.java @@ -0,0 +1,60 @@ +package org.xbib.event.yield.async; + +import org.xbib.event.yield.AsyncQuery; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.Predicate; + +public class AsyncQueryTakeWhile extends AsyncQuery { + + private final AsyncQuery upstream; + + private final Predicate p; + + private CompletableFuture subscription; + + /** + * After cancellation of upstream subscription we may still receive updates on consumer. + * To avoid propagation we must check if we have already cancelled the subscription. + * But we need a different flag from the CF subscription because this field may not be + * initialized yet on first check of the subscribe callback. + */ + private boolean finished = false; + + public AsyncQueryTakeWhile(AsyncQuery upstream, Predicate p) { + this.upstream = upstream; + this.p = p; + } + + @Override + public CompletableFuture subscribe(BiConsumer cons) { + subscription = upstream.subscribe((item, err) -> { + /* + * After cancellation of upstream subscription we may still receive updates on consumer. + * To avoid propagation we must check if we have already cancelled the subscription. + */ + if (finished) { + if (subscription != null && !subscription.isDone()) + subscription.complete(null); + return; + } + if (err != null) { + cons.accept(null, err); + return; + } + if (p.test(item)) cons.accept(item, null); + else { + if (!finished) { + finished = true; + // We need this guard because we could meet conditions + // to finish processing, yet the outer subscribe() invocation + // has not returned and the subscription is still null. + if (subscription != null) + subscription.complete(null); + } + } + }); + return subscription; + } +} diff --git a/src/main/java/org/xbib/event/yield/boxes/BoolBox.java b/src/main/java/org/xbib/event/yield/boxes/BoolBox.java new file mode 100644 index 0000000..deb98a7 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/boxes/BoolBox.java @@ -0,0 +1,31 @@ +package org.xbib.event.yield.boxes; + +public class BoolBox { + + private boolean value; + + public BoolBox(boolean value) { + this.value = value; + } + + public BoolBox() { + this(false); + } + + public boolean isTrue() { + return value; + } + + public boolean isFalse() { + return !value; + } + + public void set() { + value = true; + } + + public void set(boolean val) { + value = val; + } + +} diff --git a/src/main/java/org/xbib/event/yield/boxes/Box.java b/src/main/java/org/xbib/event/yield/boxes/Box.java new file mode 100644 index 0000000..345c274 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/boxes/Box.java @@ -0,0 +1,34 @@ +package org.xbib.event.yield.boxes; + +public class Box { + + protected T value; + + private boolean isPresent; + + public Box() { + } + + public Box(T identity) { + this.value = identity; + this.isPresent = true; + } + + public final boolean isPresent() { + return isPresent; + } + + public final T getValue() { + return value; + } + + public final T setValue(T value) { + this.value = value; + return value; + } + + public final void turnPresent(T e) { + this.setValue(e); + this.isPresent = true; + } +} diff --git a/src/main/java/org/xbib/event/yield/boxes/DoubleBox.java b/src/main/java/org/xbib/event/yield/boxes/DoubleBox.java new file mode 100644 index 0000000..ff13d55 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/boxes/DoubleBox.java @@ -0,0 +1,39 @@ +package org.xbib.event.yield.boxes; + +public class DoubleBox { + + protected double value; + + private boolean isPresent; + + public DoubleBox() { + this(Double.MIN_VALUE, false); + } + + public DoubleBox(double value, boolean isPresent) { + this.value = value; + this.isPresent = isPresent; + } + + public DoubleBox(double identity) { + this.value = identity; + this.isPresent = true; + } + + public double getValue() { + return value; + } + + public void setValue(double value) { + this.value = value; + } + + public boolean isPresent() { + return isPresent; + } + + public void turnPresent(double value) { + this.value = value; + isPresent = true; + } +} diff --git a/src/main/java/org/xbib/event/yield/boxes/IntBox.java b/src/main/java/org/xbib/event/yield/boxes/IntBox.java new file mode 100644 index 0000000..ba4c04f --- /dev/null +++ b/src/main/java/org/xbib/event/yield/boxes/IntBox.java @@ -0,0 +1,38 @@ +package org.xbib.event.yield.boxes; + +public class IntBox { + + protected int value; + + private boolean isPresent; + + public IntBox() { + this(Integer.MIN_VALUE, false); + } + + public IntBox(int value, boolean isPresent) { + this.value = value; + this.isPresent = isPresent; + } + + public IntBox(int identity) { + this.value = identity; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + public boolean isPresent() { + return isPresent; + } + + public void turnPresent(int value) { + this.value = value; + isPresent = true; + } +} diff --git a/src/main/java/org/xbib/event/yield/boxes/LongBox.java b/src/main/java/org/xbib/event/yield/boxes/LongBox.java new file mode 100644 index 0000000..81c4ed7 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/boxes/LongBox.java @@ -0,0 +1,38 @@ +package org.xbib.event.yield.boxes; + +public class LongBox { + + protected long value; + + private boolean isPresent; + + public LongBox() { + this(Long.MIN_VALUE, false); + } + + public LongBox(long value, boolean isPresent) { + this.value = value; + this.isPresent = isPresent; + } + + public LongBox(long identity) { + this.value = identity; + } + + public long getValue() { + return value; + } + + public void setValue(long value) { + this.value = value; + } + + public boolean isPresent() { + return isPresent; + } + + public void turnPresent(long value) { + this.value = value; + isPresent = true; + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Concat.java b/src/main/java/org/xbib/event/yield/ops/Concat.java new file mode 100644 index 0000000..84d7d68 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Concat.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +public class Concat implements Advancer, Traverser { + + private final Query first; + + private final Query second; + + public Concat(Query first, Query second) { + this.first = first; + this.second = second; + } + + @Override + public void traverse(Yield yield) { + this.first.traverse(yield); + this.second.traverse(yield); + } + + @Override + public boolean tryAdvance(Yield yield) { + return first.tryAdvance(yield) || second.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Distinct.java b/src/main/java/org/xbib/event/yield/ops/Distinct.java new file mode 100644 index 0000000..c6f87de --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Distinct.java @@ -0,0 +1,38 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.boxes.BoolBox; + +import java.util.HashSet; + +public class Distinct implements Advancer, Traverser { + + final HashSet mem = new HashSet<>(); + private final Query upstream; + + public Distinct(Query adv) { + this.upstream = adv; + } + + @Override + public void traverse(Yield yield) { + upstream.traverse(item -> { + if (mem.add(item)) yield.ret(item); + }); + } + + @Override + public boolean tryAdvance(Yield yield) { + final BoolBox found = new BoolBox(); + while (found.isFalse() && upstream.tryAdvance(item -> { + if (mem.add(item)) { + yield.ret(item); + found.set(); + } + })) ; + return found.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/DropWhile.java b/src/main/java/org/xbib/event/yield/ops/DropWhile.java new file mode 100644 index 0000000..4bda465 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/DropWhile.java @@ -0,0 +1,55 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +import java.util.function.Predicate; + +public class DropWhile implements Advancer, Traverser { + + private final Query upstream; + + private final Predicate predicate; + + private boolean dropped; + + public DropWhile(Query upstream, Predicate predicate) { + this.upstream = upstream; + this.predicate = predicate; + } + + @Override + public void traverse(Yield yield) { + upstream.traverse(item -> { + if (!dropped && !predicate.test(item)) { + dropped = true; + } + if (dropped) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(Yield yield) { + if (dropped) { + return upstream.tryAdvance(yield); + } else { + while (!dropped && dropNext(yield)) { + // Intentionally empty. Action specified on yield statement of tryAdvance(). + } + return dropped; + } + } + + private boolean dropNext(Yield yield) { + return upstream.tryAdvance(item -> { + if (!predicate.test(item)) { + dropped = true; + yield.ret(item); + } + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Filter.java b/src/main/java/org/xbib/event/yield/ops/Filter.java new file mode 100644 index 0000000..a1ec5d0 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Filter.java @@ -0,0 +1,44 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.boxes.BoolBox; + +import java.util.function.Predicate; + +public class Filter implements Advancer, Traverser { + + private final Query upstream; + + private final Predicate p; + + public Filter(Query adv, Predicate p) { + this.upstream = adv; + this.p = p; + } + + @Override + public void traverse(Yield yield) { + upstream.traverse(e -> { + if (p.test(e)) + yield.ret(e); + }); + } + + @Override + public boolean tryAdvance(Yield yield) { + BoolBox found = new BoolBox(); + while (found.isFalse()) { + boolean hasNext = upstream.tryAdvance(item -> { + if (p.test(item)) { + yield.ret(item); + found.set(); + } + }); + if (!hasNext) break; + } + return found.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/FlatMap.java b/src/main/java/org/xbib/event/yield/ops/FlatMap.java new file mode 100644 index 0000000..9c119e2 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/FlatMap.java @@ -0,0 +1,39 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +import java.util.function.Function; + +public class FlatMap implements Advancer, Traverser { + + private final Query upstream; + + private final Function> mapper; + + private Query src; + + public FlatMap(Query query, Function> mapper) { + this.upstream = query; + this.mapper = mapper; + src = new Query<>(Advancer.empty(), Traverser.empty()); + } + + @Override + public void traverse(Yield yield) { + upstream.traverse(elem -> + mapper.apply(elem).traverse(yield)); + + } + + @Override + public boolean tryAdvance(Yield yield) { + while (!src.tryAdvance(yield)) { + if (!upstream.tryAdvance(t -> src = mapper.apply(t))) + return false; + } + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/FromArray.java b/src/main/java/org/xbib/event/yield/ops/FromArray.java new file mode 100644 index 0000000..3561849 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/FromArray.java @@ -0,0 +1,42 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +public class FromArray implements Advancer, Traverser { + + private final U[] data; + + private int current; + + @SuppressWarnings("unchecked") + public FromArray(U... data) { + this.data = data; + this.current = 0; + } + + public boolean hasNext() { + return current < data.length; + } + + /** + * Continues from the point where tryAdvance or next left the + * internal iteration. + * + * @param yield yield + */ + @Override + public void traverse(Yield yield) { + for (int i = current; i < data.length; i++) { + yield.ret(data[i]); + } + } + + @Override + public boolean tryAdvance(Yield yield) { + if (!hasNext()) return false; + yield.ret(data[current++]); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/FromList.java b/src/main/java/org/xbib/event/yield/ops/FromList.java new file mode 100644 index 0000000..6113c8a --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/FromList.java @@ -0,0 +1,26 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +import java.util.List; +import java.util.Spliterator; + +public class FromList implements Advancer, Traverser { + private final Spliterator current; + + public FromList(List data) { + this.current = data.spliterator(); + } + + @Override + public void traverse(Yield yield) { + current.forEachRemaining(yield::ret); + } + + @Override + public boolean tryAdvance(Yield yield) { + return current.tryAdvance(yield::ret); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/FromStream.java b/src/main/java/org/xbib/event/yield/ops/FromStream.java new file mode 100644 index 0000000..8297d8b --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/FromStream.java @@ -0,0 +1,28 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +import java.util.Spliterator; +import java.util.stream.Stream; + +public class FromStream implements Advancer, Traverser { + + private final Spliterator upstream; + + public FromStream(Stream data) { + this.upstream = data.spliterator(); + } + + + @Override + public void traverse(Yield yield) { + upstream.forEachRemaining(yield::ret); + } + + @Override + public boolean tryAdvance(Yield yield) { + return upstream.tryAdvance(yield::ret); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Generate.java b/src/main/java/org/xbib/event/yield/ops/Generate.java new file mode 100644 index 0000000..582902b --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Generate.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +import java.util.function.Supplier; + +public class Generate implements Advancer, Traverser { + + private final Supplier s; + + public Generate(Supplier s) { + this.s = s; + } + + @Override + public void traverse(Yield yield) { + while (true) { + yield.ret(s.get()); + } + } + + @Override + public boolean tryAdvance(Yield yield) { + yield.ret(s.get()); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Iterate.java b/src/main/java/org/xbib/event/yield/ops/Iterate.java new file mode 100644 index 0000000..f198635 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Iterate.java @@ -0,0 +1,33 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +import java.util.function.UnaryOperator; + +public class Iterate implements Advancer, Traverser { + + private final UnaryOperator f; + + private U prev; + + public Iterate(U seed, UnaryOperator f) { + this.f = f; + this.prev = seed; + } + + @Override + public void traverse(Yield yield) { + for (U curr = prev; true; curr = f.apply(curr)) + yield.ret(curr); + } + + @Override + public boolean tryAdvance(Yield yield) { + U curr = prev; + prev = f.apply(prev); + yield.ret(curr); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Limit.java b/src/main/java/org/xbib/event/yield/ops/Limit.java new file mode 100644 index 0000000..1e23547 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Limit.java @@ -0,0 +1,38 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +public class Limit implements Advancer, Traverser { + + private final Query upstream; + + private final int n; + + int count; + + public Limit(Query upstream, int n) { + this.upstream = upstream; + this.n = n; + count = 0; + } + + @Override + public boolean tryAdvance(Yield yield) { + if (count >= n) return false; + count++; + return upstream.tryAdvance(yield); + + } + + @Override + public void traverse(Yield yield) { + if (count >= n) + throw new IllegalStateException("Traverser has already been operated on or closed!"); + while (this.tryAdvance(yield)) { + // Intentionally empty. Action specified on yield statement of tryAdvance(). + } + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Mapping.java b/src/main/java/org/xbib/event/yield/ops/Mapping.java new file mode 100644 index 0000000..8c2e4ae --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Mapping.java @@ -0,0 +1,30 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +import java.util.function.Function; + +public class Mapping implements Advancer, Traverser { + + private final Query upstream; + + private final Function mapper; + + public Mapping(Query adv, Function mapper) { + this.upstream = adv; + this.mapper = mapper; + } + + @Override + public void traverse(Yield yield) { + upstream.traverse(e -> yield.ret(mapper.apply(e))); + } + + @Override + public boolean tryAdvance(Yield yield) { + return upstream.tryAdvance(item -> yield.ret(mapper.apply(item))); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Peek.java b/src/main/java/org/xbib/event/yield/ops/Peek.java new file mode 100644 index 0000000..fcb5a1c --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Peek.java @@ -0,0 +1,36 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +import java.util.function.Consumer; + +public class Peek implements Advancer, Traverser { + + private final Query upstream; + + private final Consumer action; + + public Peek(Query adv, Consumer action) { + this.upstream = adv; + this.action = action; + } + + @Override + public void traverse(Yield yield) { + upstream.traverse(item -> { + action.accept(item); + yield.ret(item); + }); + } + + @Override + public boolean tryAdvance(Yield yield) { + return upstream.tryAdvance(item -> { + action.accept(item); + yield.ret(item); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Skip.java b/src/main/java/org/xbib/event/yield/ops/Skip.java new file mode 100644 index 0000000..3e51e85 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Skip.java @@ -0,0 +1,43 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +public class Skip implements Advancer, Traverser { + + private final Query upstream; + + private final int n; + + int index; + + public Skip(Query adv, int n) { + this.upstream = adv; + this.n = n; + index = 0; + } + + /** + * Continues from the point where tryAdvance or next left the + * internal iteration. + * + * @param yield the yield + */ + @Override + public void traverse(Yield yield) { + upstream.traverse(item -> { + if (index++ >= n) + yield.ret(item); + }); + } + + @Override + public boolean tryAdvance(Yield yield) { + for (; index < n; index++) + upstream.tryAdvance(item -> { + }); + return upstream.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/TakeWhile.java b/src/main/java/org/xbib/event/yield/ops/TakeWhile.java new file mode 100644 index 0000000..83cebdd --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/TakeWhile.java @@ -0,0 +1,44 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; + +import java.util.function.Predicate; + +public class TakeWhile implements Advancer, Traverser { + + private final Query upstream; + + private final Predicate predicate; + + private boolean hasNext; + + public TakeWhile(Query upstream, Predicate predicate) { + this.upstream = upstream; + this.predicate = predicate; + this.hasNext = true; + } + + @Override + public boolean tryAdvance(Yield yield) { + if (!hasNext) return false; // Once predicate is false it finishes the iteration + Yield takeWhile = item -> { + if (predicate.test(item)) { + yield.ret(item); + } else { + hasNext = false; + } + }; + return upstream.tryAdvance(takeWhile) && hasNext; + } + + @Override + public void traverse(Yield yield) { + upstream.shortCircuit(item -> { + if (!predicate.test(item)) Yield.bye(); + yield.ret(item); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/ops/Zip.java b/src/main/java/org/xbib/event/yield/ops/Zip.java new file mode 100644 index 0000000..afa21c1 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/ops/Zip.java @@ -0,0 +1,43 @@ +package org.xbib.event.yield.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Query; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.boxes.BoolBox; + +import java.util.function.BiFunction; + +public class Zip implements Advancer, Traverser { + + private final Query upstream; + + private final Query other; + + private final BiFunction zipper; + + public Zip(Query upstream, Query other, BiFunction zipper) { + this.upstream = upstream; + this.other = other; + this.zipper = zipper; + } + + @Override + public boolean tryAdvance(Yield yield) { + BoolBox consumed = new BoolBox(); + upstream.tryAdvance(e1 -> other.tryAdvance(e2 -> { + yield.ret(zipper.apply(e1, e2)); + consumed.set(); + })); + return consumed.isTrue(); + } + + + @Override + public void traverse(Yield yield) { + upstream.shortCircuit(e1 -> { + if (!other.tryAdvance(e2 -> yield.ret(zipper.apply(e1, e2)))) + Yield.bye(); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleAdvancer.java b/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleAdvancer.java new file mode 100644 index 0000000..fd59ff2 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleAdvancer.java @@ -0,0 +1,69 @@ +package org.xbib.event.yield.primitives.dbl; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.lng.LongAdvancer; + +import java.util.function.DoubleToLongFunction; +import java.util.function.IntToDoubleFunction; +import java.util.function.LongToDoubleFunction; +import java.util.function.ToDoubleFunction; + +/** + * Sequential traverser with both internal and external iteration approach. + */ +public interface DoubleAdvancer extends Advancer { + /** + * An DoubleAdvancer object without elements. + */ + static DoubleAdvancer empty() { + return action -> false; + } + + /** + * A DoubleAdvancer object from a generic {@link Advancer} mapped by a {@link ToDoubleFunction}. + * + * @param source {@link Advancer} with the source elements for this {@code DoubleAdvancer}. + * @param mapper {@link ToDoubleFunction} that specifies how to map the source elements double values. + */ + static DoubleAdvancer from(Advancer source, ToDoubleFunction mapper) { + return yield -> source.tryAdvance(item -> yield.ret(mapper.applyAsDouble(item))); + } + + /** + * A DoubleAdvancer object from a {@link LongAdvancer} mapped by a {@link LongToDoubleFunction}. + * + * @param source {@link LongAdvancer} with the source elements for this {@code LongAdvancer}. + * @param mapper {@link DoubleToLongFunction} that specifies how to map the source elements into double values. + */ + static DoubleAdvancer from(LongAdvancer source, LongToDoubleFunction mapper) { + return from((Advancer) source, mapper::applyAsDouble); + } + + /** + * A DoubleAdvancer object from a {@link IntAdvancer} mapped by a {@link IntToDoubleFunction}. + * + * @param source {@link IntAdvancer} with the source elements for this {@code LongAdvancer}. + * @param mapper {@link IntToDoubleFunction} that specifies how to map the source elements into double values. + */ + static DoubleAdvancer from(IntAdvancer source, IntToDoubleFunction mapper) { + return from((Advancer) source, mapper::applyAsDouble); + } + + /** + * Default advance implementation that calls the + * primitive version of it + */ + @Override + default boolean tryAdvance(Yield yield) { + DoubleYield yld = yield::ret; + return this.tryAdvance(yld); + } + + /** + * If a remaining element exists, yields that element through + * the given action. + */ + boolean tryAdvance(DoubleYield yield); +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleQuery.java b/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleQuery.java new file mode 100644 index 0000000..8519f44 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleQuery.java @@ -0,0 +1,583 @@ +package org.xbib.event.yield.primitives.dbl; + +import org.xbib.event.yield.Query; +import org.xbib.event.yield.TraversableFinishError; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.boxes.DoubleBox; +import org.xbib.event.yield.primitives.dbl.ops.DoubleConcat; +import org.xbib.event.yield.primitives.dbl.ops.DoubleDistinct; +import org.xbib.event.yield.primitives.dbl.ops.DoubleDropWhile; +import org.xbib.event.yield.primitives.dbl.ops.DoubleFilter; +import org.xbib.event.yield.primitives.dbl.ops.DoubleFlatMap; +import org.xbib.event.yield.primitives.dbl.ops.DoubleGenerate; +import org.xbib.event.yield.primitives.dbl.ops.DoubleIterate; +import org.xbib.event.yield.primitives.dbl.ops.DoubleLimit; +import org.xbib.event.yield.primitives.dbl.ops.DoubleMapToObj; +import org.xbib.event.yield.primitives.dbl.ops.DoubleMapping; +import org.xbib.event.yield.primitives.dbl.ops.DoublePeek; +import org.xbib.event.yield.primitives.dbl.ops.DoubleSkip; +import org.xbib.event.yield.primitives.dbl.ops.DoubleTakeWhile; +import org.xbib.event.yield.primitives.dbl.ops.DoubleZip; +import org.xbib.event.yield.primitives.dbl.ops.FromDoubleArray; +import org.xbib.event.yield.primitives.dbl.ops.FromDoubleStream; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.DoubleSummaryStatistics; +import java.util.List; +import java.util.OptionalDouble; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.DoubleBinaryOperator; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleFunction; +import java.util.function.DoublePredicate; +import java.util.function.DoubleSupplier; +import java.util.function.DoubleToIntFunction; +import java.util.function.DoubleToLongFunction; +import java.util.function.DoubleUnaryOperator; +import java.util.function.Function; +import java.util.function.ObjDoubleConsumer; +import java.util.function.Supplier; +import java.util.stream.DoubleStream; +import java.util.stream.StreamSupport; + +/** + * A sequence of primitive double-valued elements supporting sequential + * operations. This is the double primitive specialization of Query. + */ +public class DoubleQuery { + + private final DoubleAdvancer adv; + private final DoubleTraverser trav; + + public DoubleQuery(DoubleAdvancer adv, DoubleTraverser trav) { + this.adv = adv; + this.trav = trav; + } + + /** + * Returns a sequential ordered {@code DoubleQuery} with elements + * from the provided {@link DoubleStream} data. + */ + public static DoubleQuery fromStream(DoubleStream src) { + FromDoubleStream strm = new FromDoubleStream(src); + return new DoubleQuery(strm, strm); + } + + /** + * Returns an infinite sequential ordered {@code DoubleQuery} produced by iterative + * application of a function {@code f} to an initial element {@code seed}, + * producing a {@code DoubleQuery} consisting of {@code seed}, {@code f(seed)}, + * {@code f(f(seed))}, etc. + */ + public static DoubleQuery iterate(double seed, DoubleUnaryOperator f) { + DoubleIterate iter = new DoubleIterate(seed, f); + return new DoubleQuery(iter, iter); + } + + /** + * Returns an infinite sequential unordered {@code DoubleQuery} + * where each element is generated by the provided Supplier. + */ + public static DoubleQuery generate(DoubleSupplier s) { + DoubleGenerate gen = new DoubleGenerate(s); + return new DoubleQuery(gen, gen); + } + + /** + * Returns a sequential ordered {@code DoubleQuery} whose elements + * are the specified values in data parameter. + */ + public static DoubleQuery of(double... data) { + FromDoubleArray arr = new FromDoubleArray(data); + return new DoubleQuery(arr, arr); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + public final void forEach(DoubleYield yield) { + this.traverse(yield); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + public final void traverse(DoubleYield yield) { + this.trav.traverse(yield); + } + + /** + * If a remaining element exists, yields that element through + * the given action. + */ + public boolean tryAdvance(DoubleYield action) { + return this.adv.tryAdvance(action); + } + + /** + * Returns a {@code DoubleQuery} consisting of the elements of this {@code DoubleQuery} that match + * the given predicate. + */ + public DoubleQuery filter(DoublePredicate p) { + DoubleFilter filter = new DoubleFilter(this, p); + return new DoubleQuery(filter, filter); + } + + /** + * Returns a {@code DoubleQuery} consisting of the results of applying the given + * DoubleUnaryOperator to the elements of this {@code DoubleQuery}. + * + * @param op DoubleUnaryOperator used to map the elements of this DoubleQuery + */ + public DoubleQuery map(DoubleUnaryOperator op) { + DoubleMapping map = new DoubleMapping(this, op); + return new DoubleQuery(map, map); + } + + /** + * Returns a {@code Query} consisting of the results of applying the given + * function to the elements of this {@code DoubleQuery}. + * + * @param function DoubleFunction used to map the elements of this DoubleQuery + */ + public Query mapToObj(DoubleFunction function) { + DoubleMapToObj map = new DoubleMapToObj<>(this, function); + return new Query<>(map, map); + } + + /** + * Returns a {@code DoubleQuery} consisting of the results of replacing each element of + * this {@code DoubleQuery} with the contents of a mapped {@code DoubleQuery} produced by applying + * the provided mapping function to each element. + */ + public DoubleQuery flatMap(DoubleFunction function) { + DoubleFlatMap map = new DoubleFlatMap(this, function); + return new DoubleQuery(map, map); + } + + /** + * Returns a query consisting of the distinct elements (according to + * {@link Object#equals(Object)}) of this query. + */ + public DoubleQuery distinct() { + DoubleDistinct dist = new DoubleDistinct(this); + return new DoubleQuery(dist, dist); + } + + /** + * Returns a {@code DoubleQuery} consisting of the elements of this {@code DoubleQuery}, + * sorted according to the same logic as {@code Arrays.sort(double[] a)}. + *

+ * This is a stateful intermediate operation. + */ + public DoubleQuery sorted() { + double[] state = this.toArray(); + Arrays.sort(state); + FromDoubleArray arr = new FromDoubleArray(state); + return new DoubleQuery(arr, arr); + } + + /** + * Returns an array containing the elements of this {@code DoubleQuery}. + */ + public double[] toArray() { + List list = toList(); + double[] result = new double[list.size()]; + for (int i = 0; i < list.size(); i++) { + result[i] = list.get(i); + } + return result; + } + + /** + * Returns a List containing the elements of this {@code DoubleQuery}. + */ + public List toList() { + ArrayList result = new ArrayList<>(); + this.traverse(result::add); + return result; + } + + /** + * Returns a {@code DoubleQuery} consisting of the elements of this {@code DoubleQuery}, additionally + * performing the provided action on each element as elements are consumed + * from the resulting {@code DoubleQuery}. + */ + public DoubleQuery peek(DoubleConsumer action) { + DoublePeek peek = new DoublePeek(this, action); + return new DoubleQuery(peek, peek); + } + + /** + * Returns a {@code DoubleQuery} consisting of the elements of this query, truncated + * to be no longer than {@code n} in length. + * + * @param n maximum amount of elements to retrieve from this {@code DoubleQuery} + */ + public DoubleQuery limit(int n) { + DoubleLimit lim = new DoubleLimit(this, n); + return new DoubleQuery(lim, lim); + } + + /** + * Returns a {@code DoubleQuery} consisting of the remaining elements of this {@code DoubleQuery} + * after discarding the first {@code n} elements of the {@code DoubleQuery}. + * + * @param n number of elements to discard + */ + public DoubleQuery skip(int n) { + DoubleSkip skip = new DoubleSkip(this, n); + return new DoubleQuery(skip, skip); + } + + /** + * Returns an {@link OptionalDouble} with the resulting reduction of the elements of this {@code DoubleQuery}, + * if a reduction can be made, using the provided accumulator. + */ + public OptionalDouble reduce(DoubleBinaryOperator accumulator) { + DoubleBox box = new DoubleBox(); + if (this.tryAdvance(box::setValue)) { + return OptionalDouble.of(this.reduce(box.getValue(), accumulator)); + } else { + return OptionalDouble.empty(); + } + } + + /** + * Returns the result of the reduction of the elements of this {@code DoubleQuery}, + * using the provided identity value and accumulator. + */ + public double reduce(double identity, DoubleBinaryOperator accumulator) { + class BoxAccumulator extends DoubleBox implements DoubleYield { + public BoxAccumulator(double identity) { + super(identity); + } + + @Override + public void ret(double item) { + this.value = accumulator.applyAsDouble(value, item); + } + } + BoxAccumulator box = new BoxAccumulator(identity); + this.traverse(box); + return box.getValue(); + } + + /** + * Returns the lowest double of this {@code DoubleQuery} + */ + public OptionalDouble min() { + DoubleBox b = new DoubleBox(); + this.traverse(e -> { + if (!b.isPresent()) { + b.turnPresent(e); + } else if (e < b.getValue()) { + b.setValue(e); + } + }); + return b.isPresent() ? OptionalDouble.of(b.getValue()) : OptionalDouble.empty(); + } + + /** + * Returns the highest double of this {@code DoubleQuery} + */ + public OptionalDouble max() { + DoubleBox b = new DoubleBox(); + this.traverse(e -> { + if (!b.isPresent()) { + b.turnPresent(e); + } else if (e > b.getValue()) { + b.setValue(e); + } + }); + return b.isPresent() ? OptionalDouble.of(b.getValue()) : OptionalDouble.empty(); + } + + /** + * Returns the count of elements in this {@code DoubleQuery}. + */ + public final long count() { + class Counter implements DoubleYield { + long n = 0; + + @Override + public void ret(double item) { + ++n; + } + } + Counter c = new Counter(); + this.traverse(c); + return c.n; + } + + /** + * Returns an OptionalDouble describing the arithmetic mean of elements of this {@code DoubleQuery}, + * or an empty optional if this {@code DoubleQuery} is empty. This is a special case of a reduction. + *

+ * This is a terminal operation. + */ + public OptionalDouble average() { + double[] data = this.toArray(); + double count = data.length; + if (count == 0) { + return OptionalDouble.empty(); + } + double sum = DoubleQuery.of(data).sum(); + return OptionalDouble.of(sum / count); + } + + /** + * Returns the sum of elements in this {@code DoubleQuery} . + *

+ * This is a special case of a reduction. + */ + public double sum() { + return this.reduce(0, Double::sum); + } + + /** + * Returns an DoubleSummaryStatistics describing various summary data about + * the elements of this {@code DoubleQuery}. This is a special case of a reduction. + *

+ * This is a terminal operation. + */ + public DoubleSummaryStatistics summaryStatistics() { + return this.collect(DoubleSummaryStatistics::new, DoubleSummaryStatistics::accept); + } + + /** + * Performs a mutable reduction operation on the elements of this {@code DoubleQuery}. + * A mutable reduction is one in which the reduced value is a mutable result container, such as an ArrayList, + * and elements are incorporated by updating the state of the result rather than by replacing the result. + */ + public R collect(Supplier supplier, ObjDoubleConsumer accumulator) { + R result = supplier.get(); + this.traverse(elem -> accumulator.accept(result, elem)); + return result; + } + + /** + * Returns whether all elements of this {@code DoubleQuery} match the provided + * {@link DoublePredicate}. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code true} is returned and the predicate is not evaluated. + * + * @param p DoublePredicate used to test elements of this {@code DoubleQuery} + */ + public boolean allMatch(DoublePredicate p) { + BoolBox succeed = new BoolBox(true); + shortCircuit(item -> { + if (!p.test(item)) { + succeed.set(false); + Yield.bye(); + } + }); + return succeed.isTrue(); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or the traversal + * exited normally through the invocation of yield.bye(). + */ + public final void shortCircuit(DoubleYield yield) { + try { + this.trav.traverse(yield); + } catch (TraversableFinishError e) { + /* Proceed */ + } + } + + /** + * Returns whether no elements of this {@code DoubleQuery} match the provided + * {@link DoublePredicate}. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code true} is returned and the predicate is not evaluated. + * + * @param p DoublePredicate used to test elements of this {@code DoubleQuery} + */ + public boolean noneMatch(DoublePredicate p) { + return !this.anyMatch(p); + } + + /** + * Returns whether any elements of this {@code DoubleQuery} match the provided + * {@link DoublePredicate}. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code false} is returned and the predicate is not evaluated. + * + * @param p DoublePredicate used to test elements of this {@code DoubleQuery} + */ + public boolean anyMatch(DoublePredicate p) { + BoolBox found = new BoolBox(); + shortCircuit(item -> { + if (p.test(item)) { + found.set(); + Yield.bye(); + } + }); + return found.isTrue(); + } + + /** + * Returns an {@link OptionalDouble} describing any element of this {@code DoubleQuery}, + * or an empty {@code OptionalDouble} if this {@code DoubleQuery} is empty. + */ + public OptionalDouble findAny() { + return this.findFirst(); + } + + /** + * Returns an {@link OptionalDouble} describing the first element of this {@code DoubleQuery}, + * or an empty {@code OptionalDouble} if this {@code DoubleQuery} is empty. + */ + public OptionalDouble findFirst() { + DoubleBox box = new DoubleBox(); + this.tryAdvance(box::turnPresent); + return box.isPresent() + ? OptionalDouble.of(box.getValue()) + : OptionalDouble.empty(); + } + + /** + * Returns a {@code LongQuery} consisting of the elements of this {@code DoubleQuery}, + * converted to long. It's equivalent to calling Math.round on every element + *

+ * This is an intermediate operation. + */ + public LongQuery asLongQuery() { + return this.mapToLong(Math::round); + } + + /** + * Returns a {@code LongQuery} consisting of the results of applying the given + * function to the elements of this {@code DoubleQuery}. + * + * @param function DoubleToLongFunction used to map the elements of this DoubleQuery + */ + public LongQuery mapToLong(DoubleToLongFunction function) { + return new LongQuery(LongAdvancer.from(adv, function), LongTraverser.from(trav, function)); + } + + /** + * Returns a {@code IntQuery} consisting of the elements of this {@code DoubleQuery}, + * converted to int. It's equivalent to calling Math.round on every element + *

+ * This is an intermediate operation. + */ + public IntQuery asIntQuery() { + return this.mapToInt(v -> (int) Math.round(v)); + } + + /** + * Returns a {@code IntQuery} consisting of the results of applying the given + * function to the elements of this {@code DoubleQuery}. + * + * @param function DoubleToIntFunction used to map the elements of this DoubleQuery + */ + public IntQuery mapToInt(DoubleToIntFunction function) { + return new IntQuery(IntAdvancer.from(adv, function), IntTraverser.from(trav, function)); + } + + /** + * Returns a Stream consisting of the elements of this {@code DoubleQuery}, + * each boxed to an Double. + */ + public Query boxed() { + return new Query<>(adv, trav); + } + + public DoubleStream toStream() { + Spliterator.OfDouble iter = new Spliterators.AbstractDoubleSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { + @Override + public boolean tryAdvance(DoubleConsumer action) { + return adv.tryAdvance(action::accept); + } + + @Override + public void forEachRemaining(DoubleConsumer action) { + trav.traverse(action::accept); + } + }; + return StreamSupport.doubleStream(iter, false); + } + + /** + * The {@code then} operator lets you encapsulate a piece of an operator + * chain into a function. + * That function {@code next} is applied to this {@code DoubleQuery} to produce a new + * {@code DoubleTraverser} object that is encapsulated in the resulting {@code DoubleQuery}. + * On the other hand, the {@code nextAdv} is applied to this query to produce a new + * {@code DoubleAdvancer} object that is encapsulated in the resulting query. + */ + public final DoubleQuery then( + Function nextAdv, + Function next) { + return new DoubleQuery(nextAdv.apply(this), next.apply(this)); + } + + /** + * The {@code then} operator lets you encapsulate a piece of an operator + * chain into a function. + * That function {@code next} is applied to this {@code DoubleQuery} to produce a new + * {@code DoubleTraverser} object that is encapsulated in the resulting {@code DoubleQuery}. + */ + public final DoubleQuery then(Function next) { + DoubleAdvancer nextAdv = item -> { + throw new UnsupportedOperationException( + "Missing tryAdvance() implementation! Use the overloaded then() providing both Advancer and Traverser!"); + }; + return new DoubleQuery(nextAdv, next.apply(this)); + } + + /** + * Returns a {@code DoubleQuery} consisting of the longest prefix of elements taken from + * this {@code DoubleQuery} that match the given predicate. + */ + public final DoubleQuery takeWhile(DoublePredicate predicate) { + DoubleTakeWhile take = new DoubleTakeWhile(this, predicate); + return new DoubleQuery(take, take); + } + + /** + * Creates a concatenated {@code Query} in which the elements are + * all the elements of this {@code Query} followed by all the + * elements of the other {@code Query}. + */ + public final DoubleQuery concat(DoubleQuery other) { + DoubleConcat cat = new DoubleConcat(this, other); + return new DoubleQuery(cat, cat); + } + + /** + * Returns a {@code DoubleQuery} consisting of the remaining elements of this query + * after discarding the first sequence of elements that match the given Predicate. + */ + public final DoubleQuery dropWhile(DoublePredicate predicate) { + DoubleDropWhile drop = new DoubleDropWhile(this, predicate); + return new DoubleQuery(drop, drop); + } + + /** + * Applies a specified function to the corresponding elements of two + * sequences, producing a sequence of the results. + */ + public final DoubleQuery zip(DoubleQuery other, DoubleBinaryOperator zipper) { + DoubleZip zip = new DoubleZip(this, other, zipper); + return new DoubleQuery(zip, zip); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleTraverser.java b/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleTraverser.java new file mode 100644 index 0000000..ff2634a --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleTraverser.java @@ -0,0 +1,74 @@ +package org.xbib.event.yield.primitives.dbl; + +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.lng.LongTraverser; + +import java.util.function.DoubleToLongFunction; +import java.util.function.IntToDoubleFunction; +import java.util.function.LongToDoubleFunction; +import java.util.function.ToDoubleFunction; + +/** + * Bulk traversal. + * We use a traverse method as its first choice to + * implement Query operations. + * This is a special kind of traversal that disallows individually access. + */ +public interface DoubleTraverser extends Traverser { + /** + * An DoubleTraverser object without elements. + */ + static DoubleTraverser empty() { + return action -> { + }; + } + + /** + * A DoubleTraverser object from a generic {@link Traverser} mapped by a {@link ToDoubleFunction}. + * + * @param source {@link Traverser} with the source elements for this {@code DoubleTraverser}. + * @param mapper {@link ToDoubleFunction} that specifies how to map the source elements double values. + */ + static DoubleTraverser from(Traverser source, ToDoubleFunction mapper) { + return yield -> source.traverse(item -> yield.ret(mapper.applyAsDouble(item))); + } + + /** + * A DoubleTraverser object from a {@link LongTraverser} mapped by a {@link LongToDoubleFunction}. + * + * @param source {@link LongTraverser} with the source elements for this {@code LongTraverser}. + * @param mapper {@link DoubleToLongFunction} that specifies how to map the source elements into double values. + */ + static DoubleTraverser from(LongTraverser source, LongToDoubleFunction mapper) { + return from((Traverser) source, mapper::applyAsDouble); + } + + /** + * A DoubleTraverser object from a {@link IntTraverser} mapped by a {@link IntToDoubleFunction}. + * + * @param source {@link IntTraverser} with the source elements for this {@code LongTraverser}. + * @param mapper {@link IntToDoubleFunction} that specifies how to map the source elements into double values. + */ + static DoubleTraverser from(IntTraverser source, IntToDoubleFunction mapper) { + return from((Traverser) source, mapper::applyAsDouble); + } + + /** + * Default traverse implementation that calls the + * primitive version of it + */ + @Override + default void traverse(Yield yield) { + DoubleYield yld = yield::ret; + this.traverse(yld); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + void traverse(DoubleYield yield); +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleYield.java b/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleYield.java new file mode 100644 index 0000000..b7bb9b8 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/DoubleYield.java @@ -0,0 +1,16 @@ +package org.xbib.event.yield.primitives.dbl; + +import org.xbib.event.yield.Yield; + +/** + * Equivalent to {@code DoubleConsumer} with a yield semantics. + */ +@FunctionalInterface +public interface DoubleYield extends Yield { + void ret(double item); + + @Override + default void ret(Double item) { + this.ret((double) item); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleConcat.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleConcat.java new file mode 100644 index 0000000..2750455 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleConcat.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +public class DoubleConcat implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery first; + + private final DoubleQuery second; + + public DoubleConcat(DoubleQuery first, DoubleQuery second) { + this.first = first; + this.second = second; + } + + @Override + public void traverse(DoubleYield yield) { + this.first.traverse(yield); + this.second.traverse(yield); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + return first.tryAdvance(yield) || second.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleDistinct.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleDistinct.java new file mode 100644 index 0000000..8e0e93e --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleDistinct.java @@ -0,0 +1,41 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.HashSet; + +public class DoubleDistinct implements DoubleAdvancer, DoubleTraverser { + + final HashSet mem = new HashSet<>(); + + private final DoubleQuery upstream; + + public DoubleDistinct(DoubleQuery adv) { + this.upstream = adv; + } + + @Override + public void traverse(DoubleYield yield) { + upstream.traverse(item -> { + if (mem.add(item)) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + final BoolBox found = new BoolBox(); + while (found.isFalse() && upstream.tryAdvance(item -> { + if (mem.add(item)) { + yield.ret(item); + found.set(); + } + })) ; + return found.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleDropWhile.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleDropWhile.java new file mode 100644 index 0000000..2ddf56c --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleDropWhile.java @@ -0,0 +1,54 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.function.DoublePredicate; + +public class DoubleDropWhile implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery upstream; + private final DoublePredicate predicate; + private boolean dropped; + + public DoubleDropWhile(DoubleQuery upstream, DoublePredicate predicate) { + this.upstream = upstream; + this.predicate = predicate; + this.dropped = false; + } + + @Override + public void traverse(DoubleYield yield) { + upstream.traverse(item -> { + if (!dropped && !predicate.test(item)) { + dropped = true; + } + if (dropped) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + if (dropped) { + return upstream.tryAdvance(yield); + } else { + while (!dropped && dropNext(yield)) { + // Intentionally empty. Action specified on yield statement of tryAdvance(). + } + return dropped; + } + } + + private boolean dropNext(DoubleYield yield) { + return upstream.tryAdvance(item -> { + if (!predicate.test(item)) { + dropped = true; + yield.ret(item); + } + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleFilter.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleFilter.java new file mode 100644 index 0000000..e8a0c8b --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleFilter.java @@ -0,0 +1,45 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.function.DoublePredicate; + +public class DoubleFilter implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery upstream; + + private final DoublePredicate p; + + public DoubleFilter(DoubleQuery adv, DoublePredicate p) { + this.upstream = adv; + this.p = p; + } + + @Override + public void traverse(DoubleYield yield) { + upstream.traverse(e -> { + if (p.test(e)) { + yield.ret(e); + } + }); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + BoolBox found = new BoolBox(); + while (found.isFalse()) { + boolean hasNext = upstream.tryAdvance(item -> { + if (p.test(item)) { + yield.ret(item); + found.set(); + } + }); + if (!hasNext) break; + } + return found.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleFlatMap.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleFlatMap.java new file mode 100644 index 0000000..668be1c --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleFlatMap.java @@ -0,0 +1,37 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.function.DoubleFunction; + +public class DoubleFlatMap implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery upstream; + + private final DoubleFunction mapper; + + DoubleQuery src; + + public DoubleFlatMap(DoubleQuery query, DoubleFunction mapper) { + this.upstream = query; + this.mapper = mapper; + src = new DoubleQuery(DoubleAdvancer.empty(), DoubleTraverser.empty()); + } + + @Override + public void traverse(DoubleYield yield) { + upstream.traverse(elem -> mapper.apply(elem).traverse(yield)); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + while (!src.tryAdvance(yield)) { + if (!upstream.tryAdvance(t -> src = mapper.apply(t))) + return false; + } + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleGenerate.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleGenerate.java new file mode 100644 index 0000000..c656a59 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleGenerate.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.function.DoubleSupplier; + +public class DoubleGenerate implements DoubleAdvancer, DoubleTraverser { + private final DoubleSupplier s; + + public DoubleGenerate(DoubleSupplier s) { + this.s = s; + } + + @Override + public void traverse(DoubleYield yield) { + while (true) { + yield.ret(s.getAsDouble()); + } + } + + + @Override + public boolean tryAdvance(DoubleYield yield) { + yield.ret(s.getAsDouble()); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleIterate.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleIterate.java new file mode 100644 index 0000000..919f93d --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleIterate.java @@ -0,0 +1,40 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.function.DoubleUnaryOperator; + +public class DoubleIterate implements DoubleAdvancer, DoubleTraverser { + + private final DoubleUnaryOperator f; + + private double prev; + + public DoubleIterate(double seed, DoubleUnaryOperator f) { + this.f = f; + this.prev = seed; + } + + /** + * Continues from the point where tryAdvance or next left the + * internal iteration. + * + * @param yield yield + */ + @Override + public void traverse(DoubleYield yield) { + for (double i = prev; true; i = f.applyAsDouble(i)) { + yield.ret(i); + } + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + double curr = prev; + prev = f.applyAsDouble(prev); + yield.ret(curr); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleLimit.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleLimit.java new file mode 100644 index 0000000..de79b2c --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleLimit.java @@ -0,0 +1,37 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +public class DoubleLimit implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery upstream; + + private final int n; + + int count; + + public DoubleLimit(DoubleQuery upstream, int n) { + this.upstream = upstream; + this.n = n; + count = 0; + } + + @Override + public void traverse(DoubleYield yield) { + if (count >= n) + throw new IllegalStateException("Traverser has already been operated on or closed!"); + while (this.tryAdvance(yield)) { + // Intentionally empty. Action specified on yield statement of tryAdvance(). + } + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + if (count >= n) return false; + count++; + return upstream.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleMapToObj.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleMapToObj.java new file mode 100644 index 0000000..ca9c553 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleMapToObj.java @@ -0,0 +1,30 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; + +import java.util.function.DoubleFunction; + +public class DoubleMapToObj implements Advancer, Traverser { + + private final DoubleQuery upstream; + + private final DoubleFunction mapper; + + public DoubleMapToObj(DoubleQuery adv, DoubleFunction mapper) { + this.upstream = adv; + this.mapper = mapper; + } + + @Override + public void traverse(Yield yield) { + upstream.traverse(e -> yield.ret(mapper.apply(e))); + } + + @Override + public boolean tryAdvance(Yield yield) { + return upstream.tryAdvance(item -> yield.ret(mapper.apply(item))); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleMapping.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleMapping.java new file mode 100644 index 0000000..8652277 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleMapping.java @@ -0,0 +1,30 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.function.DoubleUnaryOperator; + +public class DoubleMapping implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery upstream; + + private final DoubleUnaryOperator mapper; + + public DoubleMapping(DoubleQuery adv, DoubleUnaryOperator mapper) { + this.upstream = adv; + this.mapper = mapper; + } + + @Override + public void traverse(DoubleYield yield) { + upstream.traverse(e -> yield.ret(mapper.applyAsDouble(e))); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + return upstream.tryAdvance(item -> yield.ret(mapper.applyAsDouble(item))); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoublePeek.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoublePeek.java new file mode 100644 index 0000000..41413a6 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoublePeek.java @@ -0,0 +1,36 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.function.DoubleConsumer; + +public class DoublePeek implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery upstream; + + private final DoubleConsumer action; + + public DoublePeek(DoubleQuery adv, DoubleConsumer action) { + this.upstream = adv; + this.action = action; + } + + @Override + public void traverse(DoubleYield yield) { + upstream.traverse(item -> { + action.accept(item); + yield.ret(item); + }); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + return upstream.tryAdvance(item -> { + action.accept(item); + yield.ret(item); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleSkip.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleSkip.java new file mode 100644 index 0000000..ce88329 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleSkip.java @@ -0,0 +1,43 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +public class DoubleSkip implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery upstream; + + private final int n; + int index; + + public DoubleSkip(DoubleQuery adv, int n) { + this.upstream = adv; + this.n = n; + index = 0; + } + + /** + * Continues from the point where tryAdvance or next left the + * internal iteration. + * + * @param yield yield + */ + @Override + public void traverse(DoubleYield yield) { + upstream.traverse(item -> { + if (index++ >= n) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + for (; index < n; index++) + upstream.tryAdvance(item -> { + }); + return upstream.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleTakeWhile.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleTakeWhile.java new file mode 100644 index 0000000..985e59d --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleTakeWhile.java @@ -0,0 +1,47 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.function.DoublePredicate; + +public class DoubleTakeWhile implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery upstream; + + private final DoublePredicate predicate; + + private boolean hasNext; + + public DoubleTakeWhile(DoubleQuery upstream, DoublePredicate predicate) { + this.upstream = upstream; + this.predicate = predicate; + this.hasNext = true; + } + + @Override + public void traverse(DoubleYield yield) { + upstream.shortCircuit(item -> { + if (!predicate.test(item)) { + Yield.bye(); + } + yield.ret(item); + }); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + if (!hasNext) return false; // Once predicate is false it finishes the iteration + DoubleYield takeWhile = item -> { + if (predicate.test(item)) { + yield.ret(item); + } else { + hasNext = false; + } + }; + return upstream.tryAdvance(takeWhile) && hasNext; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleZip.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleZip.java new file mode 100644 index 0000000..4f4f0e6 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/DoubleZip.java @@ -0,0 +1,43 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.function.DoubleBinaryOperator; + +public class DoubleZip implements DoubleAdvancer, DoubleTraverser { + + private final DoubleQuery upstream; + + private final DoubleQuery other; + + private final DoubleBinaryOperator zipper; + + public DoubleZip(DoubleQuery upstream, DoubleQuery other, DoubleBinaryOperator zipper) { + this.upstream = upstream; + this.other = other; + this.zipper = zipper; + } + + @Override + public void traverse(DoubleYield yield) { + upstream.shortCircuit(e1 -> { + if (!other.tryAdvance(e2 -> yield.ret(zipper.applyAsDouble(e1, e2)))) + Yield.bye(); + }); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + BoolBox consumed = new BoolBox(); + upstream.tryAdvance(e1 -> other.tryAdvance(e2 -> { + yield.ret(zipper.applyAsDouble(e1, e2)); + consumed.set(); + })); + return consumed.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/FromDoubleArray.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/FromDoubleArray.java new file mode 100644 index 0000000..0f4f1ac --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/FromDoubleArray.java @@ -0,0 +1,41 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +public class FromDoubleArray implements DoubleAdvancer, DoubleTraverser { + + private final double[] data; + + private int current; + + public FromDoubleArray(double... data) { + this.data = data; + this.current = 0; + } + + public boolean hasNext() { + return current < data.length; + } + + /** + * Continues from the point where tryAdvance or next left the + * internal iteration. + * + * @param yield yield + */ + @Override + public void traverse(DoubleYield yield) { + for (int i = current; i < data.length; i++) { + yield.ret(data[i]); + } + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + if (!hasNext()) return false; + yield.ret(data[current++]); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/dbl/ops/FromDoubleStream.java b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/FromDoubleStream.java new file mode 100644 index 0000000..9db10bb --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/dbl/ops/FromDoubleStream.java @@ -0,0 +1,30 @@ +package org.xbib.event.yield.primitives.dbl.ops; + +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.dbl.DoubleYield; + +import java.util.Spliterator; +import java.util.function.DoubleConsumer; +import java.util.stream.DoubleStream; + +public class FromDoubleStream implements DoubleAdvancer, DoubleTraverser { + + private final Spliterator.OfDouble upstream; + + public FromDoubleStream(DoubleStream data) { + this.upstream = data.spliterator(); + } + + @Override + public void traverse(DoubleYield yield) { + DoubleConsumer cons = yield::ret; + upstream.forEachRemaining(cons); + } + + @Override + public boolean tryAdvance(DoubleYield yield) { + DoubleConsumer cons = yield::ret; + return upstream.tryAdvance(cons); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/IntAdvancer.java b/src/main/java/org/xbib/event/yield/primitives/intgr/IntAdvancer.java new file mode 100644 index 0000000..4ae04ec --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/IntAdvancer.java @@ -0,0 +1,66 @@ +package org.xbib.event.yield.primitives.intgr; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.lng.LongAdvancer; + +import java.util.function.DoubleToIntFunction; +import java.util.function.LongToIntFunction; +import java.util.function.ToIntFunction; + +/** + * Sequential traverser with both internal and external iteration approach. + */ +public interface IntAdvancer extends Advancer { + + static IntAdvancer empty() { + return yield -> false; + } + + /** + * An IntAdvancer object from a generic {@link Advancer} mapped by a {@link ToIntFunction}. + * + * @param source {@link Advancer} with the source elements for this {@code IntAdvancer}. + * @param mapper {@link ToIntFunction} that specifies how to map the source elements into int values. + */ + static IntAdvancer from(Advancer source, ToIntFunction mapper) { + return yield -> source.tryAdvance(item -> yield.ret(mapper.applyAsInt(item))); + } + + /** + * An IntAdvancer object from a {@link DoubleAdvancer} mapped by a {@link DoubleToIntFunction}. + * + * @param source {@link DoubleAdvancer} with the source elements for this {@code IntAdvancer}. + * @param mapper {@link DoubleToIntFunction} that specifies how to map the source elements into int values. + */ + static IntAdvancer from(DoubleAdvancer source, DoubleToIntFunction mapper) { + return from((Advancer) source, mapper::applyAsInt); + } + + /** + * An IntAdvancer object from a {@link LongAdvancer} mapped by a {@link LongToIntFunction}. + * + * @param source {@link LongAdvancer} with the source elements for this {@code DoubleAdvancer}. + * @param mapper {@link LongToIntFunction} that specifies how to map the source elements into int values. + */ + static IntAdvancer from(LongAdvancer source, LongToIntFunction mapper) { + return from((Advancer) source, mapper::applyAsInt); + } + + /** + * If a remaining element exists, yields that element through + * the given action. + */ + boolean tryAdvance(IntYield yield); + + /** + * Default advancer implementation that calls the + * primitive version of it + */ + @Override + default boolean tryAdvance(Yield yield) { + IntYield yld = yield::ret; + return this.tryAdvance(yld); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/IntQuery.java b/src/main/java/org/xbib/event/yield/primitives/intgr/IntQuery.java new file mode 100644 index 0000000..15f9579 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/IntQuery.java @@ -0,0 +1,584 @@ +package org.xbib.event.yield.primitives.intgr; + +import org.xbib.event.yield.Query; +import org.xbib.event.yield.TraversableFinishError; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.boxes.IntBox; +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.intgr.ops.FromIntArray; +import org.xbib.event.yield.primitives.intgr.ops.FromIntStream; +import org.xbib.event.yield.primitives.intgr.ops.IntConcat; +import org.xbib.event.yield.primitives.intgr.ops.IntDistinct; +import org.xbib.event.yield.primitives.intgr.ops.IntDropWhile; +import org.xbib.event.yield.primitives.intgr.ops.IntFilter; +import org.xbib.event.yield.primitives.intgr.ops.IntFlatMap; +import org.xbib.event.yield.primitives.intgr.ops.IntGenerate; +import org.xbib.event.yield.primitives.intgr.ops.IntIterate; +import org.xbib.event.yield.primitives.intgr.ops.IntLimit; +import org.xbib.event.yield.primitives.intgr.ops.IntMapToObj; +import org.xbib.event.yield.primitives.intgr.ops.IntMapping; +import org.xbib.event.yield.primitives.intgr.ops.IntPeek; +import org.xbib.event.yield.primitives.intgr.ops.IntSkip; +import org.xbib.event.yield.primitives.intgr.ops.IntTakeWhile; +import org.xbib.event.yield.primitives.intgr.ops.IntZip; +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.IntSummaryStatistics; +import java.util.List; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.IntPredicate; +import java.util.function.IntSupplier; +import java.util.function.IntToDoubleFunction; +import java.util.function.IntToLongFunction; +import java.util.function.IntUnaryOperator; +import java.util.function.ObjIntConsumer; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import java.util.stream.StreamSupport; + +/** + * A sequence of primitive int-valued elements supporting sequential + * operations. This is the int primitive specialization of Query. + */ +public class IntQuery { + + private final IntAdvancer adv; + private final IntTraverser trav; + + public IntQuery(IntAdvancer adv, IntTraverser trav) { + this.adv = adv; + this.trav = trav; + } + + /** + * Returns a sequential ordered {@code IntQuery} with elements + * from the provided {@link IntStream} data. + */ + public static IntQuery fromStream(IntStream src) { + FromIntStream strm = new FromIntStream(src); + return new IntQuery(strm, strm); + } + + /** + * Returns an infinite sequential ordered {@code IntQuery} produced by iterative + * application of a function {@code f} to an initial element {@code seed}, + * producing a {@code IntQuery} consisting of {@code seed}, {@code f(seed)}, + * {@code f(f(seed))}, etc. + */ + public static IntQuery iterate(int seed, IntUnaryOperator f) { + IntIterate iter = new IntIterate(seed, f); + return new IntQuery(iter, iter); + } + + /** + * Returns an infinite sequential unordered {@code IntQuery} + * where each element is generated by the provided Supplier. + */ + public static IntQuery generate(IntSupplier s) { + IntGenerate gen = new IntGenerate(s); + return new IntQuery(gen, gen); + } + + /** + * Returns a sequential ordered {@code IntQuery} whose elements + * are the specified values in data parameter. + */ + public static IntQuery of(int... data) { + FromIntArray arr = new FromIntArray(data); + return new IntQuery(arr, arr); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + public final void forEach(IntYield yield) { + this.traverse(yield); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + public final void traverse(IntYield yield) { + this.trav.traverse(yield); + } + + /** + * If a remaining element exists, yields that element through + * the given action. + */ + public boolean tryAdvance(IntYield action) { + return this.adv.tryAdvance(action); + } + + /** + * Returns a {@code IntQuery} consisting of the elements of this {@code IntQuery} that match + * the given predicate. + */ + public IntQuery filter(IntPredicate p) { + IntFilter filter = new IntFilter(this, p); + return new IntQuery(filter, filter); + } + + /** + * Returns a {@code IntQuery} consisting of the results of applying the given + * IntUnaryOperator to the elements of this {@code IntQuery}. + * + * @param op IntUnaryOperator used to map the elements of this IntQuery + */ + public IntQuery map(IntUnaryOperator op) { + IntMapping map = new IntMapping(this, op); + return new IntQuery(map, map); + } + + /** + * Returns a {@code Query} consisting of the results of applying the given + * function to the elements of this {@code IntQuery}. + * + * @param function IntFunction used to map the elements of this IntQuery + */ + public Query mapToObj(IntFunction function) { + IntMapToObj map = new IntMapToObj<>(this, function); + return new Query<>(map, map); + } + + /** + * Returns a {@code IntQuery} consisting of the results of replacing each element of + * this {@code IntQuery} with the contents of a mapped {@code IntQuery} produced by applying + * the provided mapping function to each element. + */ + public IntQuery flatMap(IntFunction function) { + IntFlatMap map = new IntFlatMap(this, function); + return new IntQuery(map, map); + } + + /** + * Returns a query consisting of the distinct elements (according to + * {@link Object#equals(Object)}) of this query. + */ + public IntQuery distinct() { + IntDistinct dis = new IntDistinct(this); + return new IntQuery(dis, dis); + } + + /** + * Returns a {@code IntQuery} consisting of the elements of this {@code IntQuery}, + * sorted according to the same logic as {@code Arrays.sort(int[] a)}. + *

+ * This is a stateful intermediate operation. + */ + public IntQuery sorted() { + int[] state = this.toArray(); + Arrays.sort(state); + FromIntArray arr = new FromIntArray(state); + return new IntQuery(arr, arr); + } + + /** + * Returns an array containing the elements of this {@code IntQuery}. + */ + public int[] toArray() { + List list = toList(); + int[] result = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + result[i] = list.get(i); + } + return result; + } + + /** + * Returns a List containing the elements of this {@code IntQuery}. + */ + public List toList() { + ArrayList result = new ArrayList<>(); + this.traverse(result::add); + return result; + } + + /** + * Returns a {@code IntQuery} consisting of the elements of this {@code IntQuery}, additionally + * performing the provided action on each element as elements are consumed + * from the resulting {@code IntQuery}. + */ + public IntQuery peek(IntConsumer action) { + IntPeek peek = new IntPeek(this, action); + return new IntQuery(peek, peek); + } + + /** + * Returns a {@code IntQuery} consisting of the elements of this query, truncated + * to be no longer than {@code n} in length. + * + * @param n maximum amount of elements to retrieve from this {@code IntQuery} + */ + public IntQuery limit(int n) { + IntLimit lim = new IntLimit(this, n); + return new IntQuery(lim, lim); + } + + /** + * Returns a {@code IntQuery} consisting of the remaining elements of this {@code IntQuery} + * after discarding the first {@code n} elements of the {@code IntQuery}. + * + * @param n number of elements to discard + */ + public IntQuery skip(int n) { + IntSkip skip = new IntSkip(this, n); + return new IntQuery(skip, skip); + } + + /** + * Returns an {@link OptionalInt} with the resulting reduction of the elements of this {@code IntQuery}, + * if a reduction can be made, using the provided accumulator. + */ + public OptionalInt reduce(IntBinaryOperator accumulator) { + IntBox box = new IntBox(); + if (this.tryAdvance(box::setValue)) { + return OptionalInt.of(this.reduce(box.getValue(), accumulator)); + } else { + return OptionalInt.empty(); + } + } + + /** + * Returns the result of the reduction of the elements of this {@code IntQuery}, + * using the provided identity value and accumulator. + */ + public int reduce(int identity, IntBinaryOperator accumulator) { + class BoxAccumulator extends IntBox implements IntYield { + public BoxAccumulator(int identity) { + super(identity); + } + + @Override + public void ret(int item) { + this.value = accumulator.applyAsInt(value, item); + } + } + BoxAccumulator box = new BoxAccumulator(identity); + this.traverse(box); + return box.getValue(); + } + + /** + * Returns the lowest int of this {@code IntQuery} + */ + public OptionalInt min() { + IntBox b = new IntBox(); + this.traverse(e -> { + if (!b.isPresent()) { + b.turnPresent(e); + } else if (e < b.getValue()) { + b.setValue(e); + } + }); + return b.isPresent() ? OptionalInt.of(b.getValue()) : OptionalInt.empty(); + } + + /** + * Returns the highest int of this {@code IntQuery} + */ + public OptionalInt max() { + IntBox b = new IntBox(); + this.traverse(e -> { + if (!b.isPresent()) { + b.turnPresent(e); + } else if (e > b.getValue()) { + b.setValue(e); + } + }); + return b.isPresent() ? OptionalInt.of(b.getValue()) : OptionalInt.empty(); + } + + /** + * Returns the count of elements in this {@code IntQuery}. + */ + public final long count() { + class Counter implements IntYield { + long n = 0; + + @Override + public void ret(int item) { + ++n; + } + } + Counter c = new Counter(); + this.traverse(c); + return c.n; + } + + /** + * Returns an OptionalDouble describing the arithmetic mean of elements of this {@code IntQuery}, + * or an empty optional if this {@code IntQuery} is empty. This is a special case of a reduction. + *

+ * This is a terminal operation. + */ + public OptionalDouble average() { + int[] data = this.toArray(); + double count = data.length; + if (count == 0) { + return OptionalDouble.empty(); + } + double sum = IntQuery.of(data).sum(); + return OptionalDouble.of(sum / count); + } + + /** + * Returns the sum of elements in this {@code IntQuery} . + *

+ * This is a special case of a reduction. + */ + public int sum() { + return this.reduce(0, Integer::sum); + } + + /** + * Returns an IntSummaryStatistics describing various summary data about + * the elements of this {@code IntQuery}. This is a special case of a reduction. + *

+ * This is a terminal operation. + */ + public IntSummaryStatistics summaryStatistics() { + return this.collect(IntSummaryStatistics::new, IntSummaryStatistics::accept); + } + + /** + * Performs a mutable reduction operation on the elements of this {@code IntQuery}. + * A mutable reduction is one in which the reduced value is a mutable result container, such as an ArrayList, + * and elements are incorporated by updating the state of the result rather than by replacing the result. + */ + public R collect(Supplier supplier, ObjIntConsumer accumulator) { + R result = supplier.get(); + this.traverse(elem -> accumulator.accept(result, elem)); + return result; + } + + /** + * Returns whether all elements of this {@code IntQuery} match the provided + * {@link IntPredicate}. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code true} is returned and the predicate is not evaluated. + * + * @param p IntPredicate used to test elements of this {@code IntQuery} + */ + public boolean allMatch(IntPredicate p) { + BoolBox succeed = new BoolBox(true); + shortCircuit(item -> { + if (!p.test(item)) { + succeed.set(false); + Yield.bye(); + } + }); + return succeed.isTrue(); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or the traversal + * exited normally through the invocation of yield.bye(). + */ + public final void shortCircuit(IntYield yield) { + try { + this.trav.traverse(yield); + } catch (TraversableFinishError e) { + /* Proceed */ + } + } + + /** + * Returns whether no elements of this {@code IntQuery} match the provided + * {@link IntPredicate}. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code true} is returned and the predicate is not evaluated. + * + * @param p IntPredicate used to test elements of this {@code IntQuery} + */ + public boolean noneMatch(IntPredicate p) { + return !this.anyMatch(p); + } + + /** + * Returns whether any elements of this {@code IntQuery} match the provided + * {@link IntPredicate}. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code false} is returned and the predicate is not evaluated. + * + * @param p IntPredicate used to test elements of this {@code IntQuery} + */ + public boolean anyMatch(IntPredicate p) { + BoolBox found = new BoolBox(); + shortCircuit(item -> { + if (p.test(item)) { + found.set(); + Yield.bye(); + } + }); + return found.isTrue(); + } + + /** + * Returns an {@link OptionalInt} describing any element of this {@code IntQuery}, + * or an empty {@code OptionalInt} if this {@code IntQuery} is empty. + */ + public OptionalInt findAny() { + return this.findFirst(); + } + + /** + * Returns an {@link OptionalInt} describing the first element of this {@code IntQuery}, + * or an empty {@code OptionalInt} if this {@code IntQuery} is empty. + */ + public OptionalInt findFirst() { + IntBox box = new IntBox(); + this.tryAdvance(box::turnPresent); + return box.isPresent() + ? OptionalInt.of(box.getValue()) + : OptionalInt.empty(); + } + + /** + * Returns a {@code LongQuery} consisting of the elements of this {@code IntQuery}, + * converted to long. + *

+ * This is an intermediate operation. + */ + public LongQuery asLongQuery() { + return this.mapToLong(i -> i); + } + + /** + * Returns a {@code LongQuery} consisting of the results of applying the given + * function to the elements of this {@code IntQuery}. + * + * @param function IntToLongFunction used to map the elements of this IntQuery + */ + public LongQuery mapToLong(IntToLongFunction function) { + return new LongQuery(LongAdvancer.from(adv, function), LongTraverser.from(trav, function)); + } + + /** + * Returns a {@code DoubleQuery} consisting of the elements of this {@code IntQuery}, + * converted to double. + *

+ * This is an intermediate operation. + */ + public DoubleQuery asDoubleQuery() { + return this.mapToDouble(i -> i); + } + + /** + * Returns a {@code DoubleQuery} consisting of the results of applying the given + * function to the elements of this {@code IntQuery}. + * + * @param function IntToDoubleFunction used to map the elements of this IntQuery + */ + public DoubleQuery mapToDouble(IntToDoubleFunction function) { + return new DoubleQuery(DoubleAdvancer.from(adv, function), DoubleTraverser.from(trav, function)); + } + + /** + * Returns a Stream consisting of the elements of this {@code IntQuery}, + * each boxed to an Integer. + */ + public Query boxed() { + return new Query<>(adv, trav); + } + + public IntStream toStream() { + Spliterator.OfInt iter = new Spliterators.AbstractIntSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { + @Override + public boolean tryAdvance(IntConsumer action) { + return adv.tryAdvance(action::accept); + } + + @Override + public void forEachRemaining(IntConsumer action) { + trav.traverse(action::accept); + } + }; + return StreamSupport.intStream(iter, false); + } + + + /** + * The {@code then} operator lets you encapsulate a piece of an operator + * chain into a function. + * That function {@code next} is applied to this {@code DoubleQuery} to produce a new + * {@code IntTraverser} object that is encapsulated in the resulting {@code DoubleQuery}. + * On the other hand, the {@code nextAdv} is applied to this query to produce a new + * {@code IntAdvancer} object that is encapsulated in the resulting query. + */ + public final IntQuery then( + Function nextAdv, + Function next) { + return new IntQuery(nextAdv.apply(this), next.apply(this)); + } + + /** + * The {@code then} operator lets you encapsulate a piece of an operator + * chain into a function. + * That function {@code next} is applied to this {@code IntQuery} to produce a new + * {@code IntTraverser} object that is encapsulated in the resulting {@code IntQuery}. + */ + public final IntQuery then(Function next) { + IntAdvancer nextAdv = item -> { + throw new UnsupportedOperationException( + "Missing tryAdvance() implementation! Use the overloaded then() providing both Advancer and Traverser!"); + }; + return new IntQuery(nextAdv, next.apply(this)); + } + + /** + * Returns a {@code IntQuery} consisting of the longest prefix of elements taken from + * this {@code IntQuery} that match the given predicate. + */ + public final IntQuery takeWhile(IntPredicate predicate) { + IntTakeWhile take = new IntTakeWhile(this, predicate); + return new IntQuery(take, take); + } + + /** + * Creates a concatenated {@code Query} in which the elements are + * all the elements of this {@code Query} followed by all the + * elements of the other {@code Query}. + */ + public final IntQuery concat(IntQuery other) { + IntConcat cat = new IntConcat(this, other); + return new IntQuery(cat, cat); + } + + /** + * Returns a {@code IntQuery} consisting of the remaining elements of this query + * after discarding the first sequence of elements that match the given Predicate. + */ + public final IntQuery dropWhile(IntPredicate predicate) { + IntDropWhile drop = new IntDropWhile(this, predicate); + return new IntQuery(drop, drop); + } + + /** + * Applies a specified function to the corresponding elements of two + * sequences, producing a sequence of the results. + */ + public final IntQuery zip(IntQuery other, IntBinaryOperator zipper) { + IntZip zip = new IntZip(this, other, zipper); + return new IntQuery(zip, zip); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/IntTraverser.java b/src/main/java/org/xbib/event/yield/primitives/intgr/IntTraverser.java new file mode 100644 index 0000000..2589c0e --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/IntTraverser.java @@ -0,0 +1,70 @@ +package org.xbib.event.yield.primitives.intgr; + +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.lng.LongTraverser; + +import java.util.function.DoubleToIntFunction; +import java.util.function.LongToIntFunction; +import java.util.function.ToIntFunction; + +/** + * Bulk traversal. + * We use a traverse method as its first choice to + * implement Query operations. + * This is a special kind of traversal that disallows individually access. + */ +public interface IntTraverser extends Traverser { + static IntTraverser empty() { + return yield -> { + }; + } + + /** + * An IntTraverser object from a generic {@link Traverser} mapped by a {@link ToIntFunction}. + * + * @param source {@link Traverser} with the source elements for this {@code IntTraverser}. + * @param mapper {@link ToIntFunction} that specifies how to map the source elements into int values. + */ + static IntTraverser from(Traverser source, ToIntFunction mapper) { + return yield -> source.traverse(item -> yield.ret(mapper.applyAsInt(item))); + } + + /** + * An IntTraverser object from a {@link DoubleTraverser} mapped by a {@link DoubleToIntFunction}. + * + * @param source {@link DoubleTraverser} with the source elements for this {@code IntTraverser}. + * @param mapper {@link DoubleToIntFunction} that specifies how to map the source elements into int values. + */ + static IntTraverser from(DoubleTraverser source, DoubleToIntFunction mapper) { + return from((Traverser) source, mapper::applyAsInt); + } + + /** + * An IntTraverser object from a {@link LongTraverser} mapped by a {@link LongToIntFunction}. + * + * @param source {@link LongTraverser} with the source elements for this {@code DoubleTraverser}. + * @param mapper {@link LongToIntFunction} that specifies how to map the source elements into int values. + */ + static IntTraverser from(LongTraverser source, LongToIntFunction mapper) { + return from((Traverser) source, mapper::applyAsInt); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + void traverse(IntYield yield); + + /** + * Default traverse implementation that calls the + * primitive version of it + */ + @Override + default void traverse(Yield yield) { + IntYield yld = yield::ret; + this.traverse(yld); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/IntYield.java b/src/main/java/org/xbib/event/yield/primitives/intgr/IntYield.java new file mode 100644 index 0000000..0b98827 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/IntYield.java @@ -0,0 +1,16 @@ +package org.xbib.event.yield.primitives.intgr; + +import org.xbib.event.yield.Yield; + +/** + * Equivalent to {@code IntConsumer} with a yield semantics. + */ +@FunctionalInterface +public interface IntYield extends Yield { + void ret(int item); + + @Override + default void ret(Integer item) { + this.ret((int) item); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/FromIntArray.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/FromIntArray.java new file mode 100644 index 0000000..3b58933 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/FromIntArray.java @@ -0,0 +1,41 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +public class FromIntArray implements IntAdvancer, IntTraverser { + + private final int[] data; + + private int current; + + public FromIntArray(int... data) { + this.data = data; + this.current = 0; + } + + public boolean hasNext() { + return current < data.length; + } + + /** + * Continues from the point where tryAdvance or next left the + * internal iteration. + * + * @param yield + */ + @Override + public void traverse(IntYield yield) { + for (int i = current; i < data.length; i++) { + yield.ret(data[i]); + } + } + + @Override + public boolean tryAdvance(IntYield yield) { + if (!hasNext()) return false; + yield.ret(data[current++]); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/FromIntStream.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/FromIntStream.java new file mode 100644 index 0000000..fa2dfc3 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/FromIntStream.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.Spliterator; +import java.util.function.IntConsumer; +import java.util.stream.IntStream; + +public class FromIntStream implements IntAdvancer, IntTraverser { + private final Spliterator.OfInt upstream; + + public FromIntStream(IntStream data) { + this.upstream = data.spliterator(); + } + + @Override + public void traverse(IntYield yield) { + IntConsumer cons = yield::ret; + upstream.forEachRemaining(cons); + } + + @Override + public boolean tryAdvance(IntYield yield) { + IntConsumer cons = yield::ret; + return upstream.tryAdvance(cons); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntConcat.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntConcat.java new file mode 100644 index 0000000..5661dc6 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntConcat.java @@ -0,0 +1,27 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +public class IntConcat implements IntAdvancer, IntTraverser { + private final IntQuery first; + private final IntQuery second; + + public IntConcat(IntQuery first, IntQuery second) { + this.first = first; + this.second = second; + } + + @Override + public void traverse(IntYield yield) { + this.first.traverse(yield); + this.second.traverse(yield); + } + + @Override + public boolean tryAdvance(IntYield yield) { + return first.tryAdvance(yield) || second.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntDistinct.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntDistinct.java new file mode 100644 index 0000000..6d66b66 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntDistinct.java @@ -0,0 +1,39 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.HashSet; + +public class IntDistinct implements IntAdvancer, IntTraverser { + final HashSet mem = new HashSet<>(); + private final IntQuery upstream; + + public IntDistinct(IntQuery adv) { + this.upstream = adv; + } + + @Override + public void traverse(IntYield yield) { + upstream.traverse(item -> { + if (mem.add(item)) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(IntYield yield) { + final BoolBox found = new BoolBox(); + while (found.isFalse() && upstream.tryAdvance(item -> { + if (mem.add(item)) { + yield.ret(item); + found.set(); + } + })) ; + return found.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntDropWhile.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntDropWhile.java new file mode 100644 index 0000000..0b56b93 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntDropWhile.java @@ -0,0 +1,55 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.function.IntPredicate; + +public class IntDropWhile implements IntAdvancer, IntTraverser { + + private final IntQuery upstream; + private final IntPredicate predicate; + private boolean dropped; + + public IntDropWhile(IntQuery upstream, IntPredicate predicate) { + this.upstream = upstream; + this.predicate = predicate; + this.dropped = false; + } + + @Override + public void traverse(IntYield yield) { + upstream.traverse(item -> { + if (!dropped && !predicate.test(item)) { + dropped = true; + } + if (dropped) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(IntYield yield) { + if (dropped) { + return upstream.tryAdvance(yield); + } else { + while (!dropped && dropNext(yield)) { + // Intentionally empty. Action specified on yield statement of tryAdvance(). + } + return dropped; + } + + } + + private boolean dropNext(IntYield yield) { + return upstream.tryAdvance(item -> { + if (!predicate.test(item)) { + dropped = true; + yield.ret(item); + } + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntFilter.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntFilter.java new file mode 100644 index 0000000..4b104dd --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntFilter.java @@ -0,0 +1,43 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.function.IntPredicate; + +public class IntFilter implements IntAdvancer, IntTraverser { + private final IntQuery upstream; + private final IntPredicate p; + + public IntFilter(IntQuery adv, IntPredicate p) { + this.upstream = adv; + this.p = p; + } + + @Override + public void traverse(IntYield yield) { + upstream.traverse(e -> { + if (p.test(e)) { + yield.ret(e); + } + }); + } + + @Override + public boolean tryAdvance(IntYield yield) { + BoolBox found = new BoolBox(); + while (found.isFalse()) { + boolean hasNext = upstream.tryAdvance(item -> { + if (p.test(item)) { + yield.ret(item); + found.set(); + } + }); + if (!hasNext) break; + } + return found.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntFlatMap.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntFlatMap.java new file mode 100644 index 0000000..bb03427 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntFlatMap.java @@ -0,0 +1,34 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.function.IntFunction; + +public class IntFlatMap implements IntAdvancer, IntTraverser { + private final IntQuery upstream; + private final IntFunction mapper; + IntQuery src; + + public IntFlatMap(IntQuery query, IntFunction mapper) { + this.upstream = query; + this.mapper = mapper; + src = new IntQuery(IntAdvancer.empty(), IntTraverser.empty()); + } + + @Override + public void traverse(IntYield yield) { + upstream.traverse(elem -> mapper.apply(elem).traverse(yield)); + } + + @Override + public boolean tryAdvance(IntYield yield) { + while (!src.tryAdvance(yield)) { + if (!upstream.tryAdvance(t -> src = mapper.apply(t))) + return false; + } + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntGenerate.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntGenerate.java new file mode 100644 index 0000000..ff08ff7 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntGenerate.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.function.IntSupplier; + +public class IntGenerate implements IntAdvancer, IntTraverser { + private final IntSupplier s; + + public IntGenerate(IntSupplier s) { + this.s = s; + } + + @SuppressWarnings("java:S2189") + @Override + public void traverse(IntYield yield) { + while (true) { + yield.ret(s.getAsInt()); + } + } + + @Override + public boolean tryAdvance(IntYield yield) { + yield.ret(s.getAsInt()); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntIterate.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntIterate.java new file mode 100644 index 0000000..b6c8816 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntIterate.java @@ -0,0 +1,38 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.function.IntUnaryOperator; + +public class IntIterate implements IntAdvancer, IntTraverser { + private final IntUnaryOperator f; + private int prev; + + public IntIterate(int seed, IntUnaryOperator f) { + this.f = f; + this.prev = seed; + } + + /** + * Continues from the point where tryAdvance or next left the + * internal iteration. + * + * @param yield + */ + @Override + public void traverse(IntYield yield) { + for (int i = prev; true; i = f.applyAsInt(i)) { + yield.ret(i); + } + } + + @Override + public boolean tryAdvance(IntYield yield) { + int curr = prev; + prev = f.applyAsInt(prev); + yield.ret(curr); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntLimit.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntLimit.java new file mode 100644 index 0000000..1259d91 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntLimit.java @@ -0,0 +1,34 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +public class IntLimit implements IntAdvancer, IntTraverser { + private final IntQuery upstream; + private final int n; + int count; + + public IntLimit(IntQuery upstream, int n) { + this.upstream = upstream; + this.n = n; + count = 0; + } + + @Override + public void traverse(IntYield yield) { + if (count >= n) + throw new IllegalStateException("Traverser has already been operated on or closed!"); + while (this.tryAdvance(yield)) { + // Intentionally empty. Action specified on yield statement of tryAdvance(). + } + } + + @Override + public boolean tryAdvance(IntYield yield) { + if (count >= n) return false; + count++; + return upstream.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntMapToObj.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntMapToObj.java new file mode 100644 index 0000000..7a18e73 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntMapToObj.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.intgr.IntQuery; + +import java.util.function.IntFunction; + +public class IntMapToObj implements Advancer, Traverser { + + private final IntQuery upstream; + private final IntFunction mapper; + + public IntMapToObj(IntQuery adv, IntFunction mapper) { + this.upstream = adv; + this.mapper = mapper; + } + + @Override + public void traverse(Yield yield) { + upstream.traverse(e -> yield.ret(mapper.apply(e))); + } + + @Override + public boolean tryAdvance(Yield yield) { + return upstream.tryAdvance(item -> yield.ret(mapper.apply(item))); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntMapping.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntMapping.java new file mode 100644 index 0000000..94cfa78 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntMapping.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.function.IntUnaryOperator; + +public class IntMapping implements IntAdvancer, IntTraverser { + + private final IntQuery upstream; + private final IntUnaryOperator mapper; + + public IntMapping(IntQuery adv, IntUnaryOperator mapper) { + this.upstream = adv; + this.mapper = mapper; + } + + @Override + public void traverse(IntYield yield) { + upstream.traverse(e -> yield.ret(mapper.applyAsInt(e))); + } + + @Override + public boolean tryAdvance(IntYield yield) { + return upstream.tryAdvance(item -> yield.ret(mapper.applyAsInt(item))); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntPeek.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntPeek.java new file mode 100644 index 0000000..3b37a15 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntPeek.java @@ -0,0 +1,34 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.function.IntConsumer; + +public class IntPeek implements IntAdvancer, IntTraverser { + private final IntQuery upstream; + private final IntConsumer action; + + public IntPeek(IntQuery adv, IntConsumer action) { + this.upstream = adv; + this.action = action; + } + + @Override + public void traverse(IntYield yield) { + upstream.traverse(item -> { + action.accept(item); + yield.ret(item); + }); + } + + @Override + public boolean tryAdvance(IntYield yield) { + return upstream.tryAdvance(item -> { + action.accept(item); + yield.ret(item); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntSkip.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntSkip.java new file mode 100644 index 0000000..6a2d020 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntSkip.java @@ -0,0 +1,44 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +public class IntSkip implements IntAdvancer, IntTraverser { + + private final IntQuery upstream; + + private final int n; + + int index; + + public IntSkip(IntQuery adv, int n) { + this.upstream = adv; + this.n = n; + index = 0; + } + + /** + * Continues from the point where tryAdvance or next left the + * internal iteration. + * + * @param yield + */ + @Override + public void traverse(IntYield yield) { + upstream.traverse(item -> { + if (index++ >= n) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(IntYield yield) { + for (; index < n; index++) + upstream.tryAdvance(item -> { + }); + return upstream.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntTakeWhile.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntTakeWhile.java new file mode 100644 index 0000000..3435157 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntTakeWhile.java @@ -0,0 +1,47 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.function.IntPredicate; + +public class IntTakeWhile implements IntAdvancer, IntTraverser { + + private final IntQuery upstream; + + private final IntPredicate predicate; + + private boolean hasNext; + + public IntTakeWhile(IntQuery upstream, IntPredicate predicate) { + this.upstream = upstream; + this.predicate = predicate; + this.hasNext = true; + } + + @Override + public void traverse(IntYield yield) { + upstream.shortCircuit(item -> { + if (!predicate.test(item)) { + Yield.bye(); + } + yield.ret(item); + }); + } + + @Override + public boolean tryAdvance(IntYield yield) { + if (!hasNext) return false; // Once predicate is false it finishes the iteration + IntYield takeWhile = item -> { + if (predicate.test(item)) { + yield.ret(item); + } else { + hasNext = false; + } + }; + return upstream.tryAdvance(takeWhile) && hasNext; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntZip.java b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntZip.java new file mode 100644 index 0000000..9d469ff --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/intgr/ops/IntZip.java @@ -0,0 +1,43 @@ +package org.xbib.event.yield.primitives.intgr.ops; + +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.intgr.IntYield; + +import java.util.function.IntBinaryOperator; + +public class IntZip implements IntAdvancer, IntTraverser { + + private final IntQuery upstream; + + private final IntQuery other; + + private final IntBinaryOperator zipper; + + public IntZip(IntQuery upstream, IntQuery other, IntBinaryOperator zipper) { + this.upstream = upstream; + this.other = other; + this.zipper = zipper; + } + + @Override + public void traverse(IntYield yield) { + upstream.shortCircuit(e1 -> { + if (!other.tryAdvance(e2 -> yield.ret(zipper.applyAsInt(e1, e2)))) + Yield.bye(); + }); + } + + @Override + public boolean tryAdvance(IntYield yield) { + BoolBox consumed = new BoolBox(); + upstream.tryAdvance(e1 -> other.tryAdvance(e2 -> { + yield.ret(zipper.applyAsInt(e1, e2)); + consumed.set(); + })); + return consumed.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/LongAdvancer.java b/src/main/java/org/xbib/event/yield/primitives/lng/LongAdvancer.java new file mode 100644 index 0000000..65c49f2 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/LongAdvancer.java @@ -0,0 +1,69 @@ +package org.xbib.event.yield.primitives.lng; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; + +import java.util.function.DoubleToLongFunction; +import java.util.function.IntToLongFunction; +import java.util.function.ToLongFunction; + +/** + * Sequential traverser with both longernal and external iteration approach. + */ +public interface LongAdvancer extends Advancer { + + /** + * An LongAdvancer object without elements. + */ + static LongAdvancer empty() { + return yield -> false; + } + + /** + * An LongAdvancer object from a generic {@link Advancer} mapped by a {@link ToLongFunction}. + * + * @param source {@link Advancer} with the source elements for this {@code LongAdvancer}. + * @param mapper {@link ToLongFunction} that specifies how to map the source elements longo long values. + */ + static LongAdvancer from(Advancer source, ToLongFunction mapper) { + return yield -> source.tryAdvance(item -> yield.ret(mapper.applyAsLong(item))); + } + + /** + * An LongAdvancer object from a {@link DoubleAdvancer} mapped by a {@link DoubleToLongFunction}. + * + * @param source {@link DoubleAdvancer} with the source elements for this {@code LongAdvancer}. + * @param mapper {@link DoubleToLongFunction} that specifies how to map the source elements longo long values. + */ + static LongAdvancer from(DoubleAdvancer source, DoubleToLongFunction mapper) { + return from((Advancer) source, mapper::applyAsLong); + } + + /** + * An LongAdvancer object from a {@link IntAdvancer} mapped by a {@link IntToLongFunction}. + * + * @param source {@link IntAdvancer} with the source elements for this {@code LongAdvancer}. + * @param mapper {@link IntToLongFunction} that specifies how to map the source elements longo long values. + */ + static LongAdvancer from(IntAdvancer source, IntToLongFunction mapper) { + return from((Advancer) source, mapper::applyAsLong); + } + + /** + * If a remaining element exists, yields that element through + * the given action. + */ + boolean tryAdvance(LongYield yield); + + /** + * Default advance implementation that calls the + * primitive version of it + */ + @Override + default boolean tryAdvance(Yield yield) { + LongYield yld = yield::ret; + return this.tryAdvance(yld); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/LongQuery.java b/src/main/java/org/xbib/event/yield/primitives/lng/LongQuery.java new file mode 100644 index 0000000..c77b153 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/LongQuery.java @@ -0,0 +1,583 @@ +package org.xbib.event.yield.primitives.lng; + +import org.xbib.event.yield.Query; +import org.xbib.event.yield.TraversableFinishError; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.boxes.LongBox; +import org.xbib.event.yield.primitives.dbl.DoubleAdvancer; +import org.xbib.event.yield.primitives.dbl.DoubleQuery; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntQuery; +import org.xbib.event.yield.primitives.intgr.IntTraverser; +import org.xbib.event.yield.primitives.lng.ops.FromLongArray; +import org.xbib.event.yield.primitives.lng.ops.FromLongStream; +import org.xbib.event.yield.primitives.lng.ops.LongConcat; +import org.xbib.event.yield.primitives.lng.ops.LongDistinct; +import org.xbib.event.yield.primitives.lng.ops.LongDropWhile; +import org.xbib.event.yield.primitives.lng.ops.LongFilter; +import org.xbib.event.yield.primitives.lng.ops.LongFlatMap; +import org.xbib.event.yield.primitives.lng.ops.LongGenerate; +import org.xbib.event.yield.primitives.lng.ops.LongIterate; +import org.xbib.event.yield.primitives.lng.ops.LongLimit; +import org.xbib.event.yield.primitives.lng.ops.LongMap; +import org.xbib.event.yield.primitives.lng.ops.LongMapToObj; +import org.xbib.event.yield.primitives.lng.ops.LongPeek; +import org.xbib.event.yield.primitives.lng.ops.LongSkip; +import org.xbib.event.yield.primitives.lng.ops.LongTakeWhile; +import org.xbib.event.yield.primitives.lng.ops.LongZip; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.LongSummaryStatistics; +import java.util.OptionalDouble; +import java.util.OptionalLong; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Function; +import java.util.function.LongBinaryOperator; +import java.util.function.LongConsumer; +import java.util.function.LongFunction; +import java.util.function.LongPredicate; +import java.util.function.LongSupplier; +import java.util.function.LongToDoubleFunction; +import java.util.function.LongToIntFunction; +import java.util.function.LongUnaryOperator; +import java.util.function.ObjLongConsumer; +import java.util.function.Supplier; +import java.util.stream.LongStream; +import java.util.stream.StreamSupport; + +/** + * A sequence of primitive long-valued elements supporting sequential + * operations. This is the long primitive specialization of Query. + */ +public class LongQuery { + + private final LongAdvancer adv; + private final LongTraverser trav; + + public LongQuery(LongAdvancer adv, LongTraverser trav) { + this.adv = adv; + this.trav = trav; + } + + /** + * Returns a sequential ordered {@code LongQuery} with elements + * from the provided {@link LongStream} data. + */ + public static LongQuery fromStream(LongStream src) { + FromLongStream strm = new FromLongStream(src); + return new LongQuery(strm, strm); + } + + /** + * Returns an infinite sequential ordered {@code LongQuery} produced by iterative + * application of a function {@code f} to an initial element {@code seed}, + * producing a {@code LongQuery} consisting of {@code seed}, {@code f(seed)}, + * {@code f(f(seed))}, etc. + */ + public static LongQuery iterate(long seed, LongUnaryOperator f) { + LongIterate iter = new LongIterate(seed, f); + return new LongQuery(iter, iter); + } + + /** + * Returns an infinite sequential unordered {@code LongQuery} + * where each element is generated by the provided Supplier. + */ + public static LongQuery generate(LongSupplier s) { + LongGenerate gen = new LongGenerate(s); + return new LongQuery(gen, gen); + } + + /** + * Returns a sequential ordered {@code LongQuery} whose elements + * are the specified values in data parameter. + */ + public static LongQuery of(long... data) { + FromLongArray arr = new FromLongArray(data); + return new LongQuery(arr, arr); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + public final void forEach(LongYield yield) { + this.traverse(yield); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + public final void traverse(LongYield yield) { + this.trav.traverse(yield); + } + + /** + * If a remaining element exists, yields that element through + * the given action. + */ + public boolean tryAdvance(LongYield action) { + return this.adv.tryAdvance(action); + } + + /** + * Returns a {@code LongQuery} consisting of the elements of this {@code LongQuery} that match + * the given predicate. + */ + public LongQuery filter(LongPredicate p) { + LongFilter filter = new LongFilter(this, p); + return new LongQuery(filter, filter); + } + + /** + * Returns a {@code LongQuery} consisting of the results of applying the given + * LongUnaryOperator to the elements of this {@code LongQuery}. + * + * @param op LongUnaryOperator used to map the elements of this LongQuery + */ + public LongQuery map(LongUnaryOperator op) { + LongMap map = new LongMap(this, op); + return new LongQuery(map, map); + } + + /** + * Returns a {@code Query} consisting of the results of applying the given + * function to the elements of this {@code LongQuery}. + * + * @param function LongFunction used to map the elements of this LongQuery + */ + public Query mapToObj(LongFunction function) { + LongMapToObj map = new LongMapToObj<>(this, function); + return new Query<>(map, map); + } + + /** + * Returns a {@code LongQuery} consisting of the results of replacing each element of + * this {@code LongQuery} with the contents of a mapped {@code LongQuery} produced by applying + * the provided mapping function to each element. + */ + public LongQuery flatMap(LongFunction function) { + LongFlatMap map = new LongFlatMap(this, function); + return new LongQuery(map, map); + } + + /** + * Returns a query consisting of the distinct elements (according to + * {@link Object#equals(Object)}) of this query. + */ + public LongQuery distinct() { + LongDistinct dis = new LongDistinct(this); + return new LongQuery(dis, dis); + } + + /** + * Returns a {@code LongQuery} consisting of the elements of this {@code LongQuery}, + * sorted according to the same logic as {@code Arrays.sort(long[] a)}. + *

+ * This is a stateful intermediate operation. + */ + public LongQuery sorted() { + long[] state = this.toArray(); + Arrays.sort(state); + FromLongArray arr = new FromLongArray(state); + return new LongQuery(arr, arr); + } + + /** + * Returns an array containing the elements of this {@code LongQuery}. + */ + public long[] toArray() { + List list = toList(); + long[] result = new long[list.size()]; + for (int i = 0; i < list.size(); i++) { + result[i] = list.get(i); + } + return result; + } + + /** + * Returns a List containing the elements of this {@code LongQuery}. + */ + public List toList() { + ArrayList result = new ArrayList<>(); + this.traverse(result::add); + return result; + } + + /** + * Returns a {@code LongQuery} consisting of the elements of this {@code LongQuery}, additionally + * performing the provided action on each element as elements are consumed + * from the resulting {@code LongQuery}. + */ + public LongQuery peek(LongConsumer action) { + LongPeek peek = new LongPeek(this, action); + return new LongQuery(peek, peek); + } + + /** + * Returns a {@code LongQuery} consisting of the elements of this query, truncated + * to be no longer than {@code n} in length. + * + * @param n maximum amount of elements to retrieve from this {@code LongQuery} + */ + public LongQuery limit(int n) { + LongLimit lim = new LongLimit(this, n); + return new LongQuery(lim, lim); + } + + /** + * Returns a {@code LongQuery} consisting of the remaining elements of this {@code LongQuery} + * after discarding the first {@code n} elements of the {@code LongQuery}. + * + * @param n number of elements to discard + */ + public LongQuery skip(int n) { + LongSkip skip = new LongSkip(this, n); + return new LongQuery(skip, skip); + } + + /** + * Returns an {@link OptionalLong} with the resulting reduction of the elements of this {@code LongQuery}, + * if a reduction can be made, using the provided accumulator. + */ + public OptionalLong reduce(LongBinaryOperator accumulator) { + LongBox box = new LongBox(); + if (this.tryAdvance(box::setValue)) { + return OptionalLong.of(this.reduce(box.getValue(), accumulator)); + } else { + return OptionalLong.empty(); + } + } + + /** + * Returns the result of the reduction of the elements of this {@code LongQuery}, + * using the provided identity value and accumulator. + */ + public long reduce(long identity, LongBinaryOperator accumulator) { + class BoxAccumulator extends LongBox implements LongYield { + public BoxAccumulator(long identity) { + super(identity); + } + + @Override + public void ret(long item) { + this.value = accumulator.applyAsLong(value, item); + } + } + BoxAccumulator box = new BoxAccumulator(identity); + this.traverse(box); + return box.getValue(); + } + + /** + * Returns the lowest long of this {@code LongQuery} + */ + public OptionalLong min() { + LongBox b = new LongBox(); + this.traverse(e -> { + if (!b.isPresent()) { + b.turnPresent(e); + } else if (e < b.getValue()) { + b.setValue(e); + } + }); + return b.isPresent() ? OptionalLong.of(b.getValue()) : OptionalLong.empty(); + } + + /** + * Returns the highest long of this {@code LongQuery} + */ + public OptionalLong max() { + LongBox b = new LongBox(); + this.traverse(e -> { + if (!b.isPresent()) { + b.turnPresent(e); + } else if (e > b.getValue()) { + b.setValue(e); + } + }); + return b.isPresent() ? OptionalLong.of(b.getValue()) : OptionalLong.empty(); + } + + /** + * Returns the count of elements in this {@code LongQuery}. + */ + public final long count() { + class Counter implements LongYield { + long n = 0; + + @Override + public void ret(long item) { + ++n; + } + } + Counter c = new Counter(); + this.traverse(c); + return c.n; + } + + /** + * Returns an OptionalDouble describing the arithmetic mean of elements of this {@code LongQuery}, + * or an empty optional if this {@code LongQuery} is empty. This is a special case of a reduction. + *

+ * This is a terminal operation. + */ + public OptionalDouble average() { + long[] data = this.toArray(); + double count = data.length; + if (count == 0) { + return OptionalDouble.empty(); + } + double sum = LongQuery.of(data).sum(); + return OptionalDouble.of(sum / count); + } + + /** + * Returns the sum of elements in this {@code LongQuery} . + *

+ * This is a special case of a reduction. + */ + public long sum() { + return this.reduce(0, Long::sum); + } + + /** + * Returns an LongSummaryStatistics describing various summary data about + * the elements of this {@code LongQuery}. This is a special case of a reduction. + *

+ * This is a terminal operation. + */ + public LongSummaryStatistics summaryStatistics() { + return this.collect(LongSummaryStatistics::new, LongSummaryStatistics::accept); + } + + /** + * Performs a mutable reduction operation on the elements of this {@code LongQuery}. + * A mutable reduction is one in which the reduced value is a mutable result container, such as an ArrayList, + * and elements are incorporated by updating the state of the result rather than by replacing the result. + */ + public R collect(Supplier supplier, ObjLongConsumer accumulator) { + R result = supplier.get(); + this.traverse(elem -> accumulator.accept(result, elem)); + return result; + } + + /** + * Returns whether all elements of this {@code LongQuery} match the provided + * {@link LongPredicate}. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code true} is returned and the predicate is not evaluated. + * + * @param p LongPredicate used to test elements of this {@code LongQuery} + */ + public boolean allMatch(LongPredicate p) { + BoolBox succeed = new BoolBox(true); + shortCircuit(item -> { + if (!p.test(item)) { + succeed.set(false); + Yield.bye(); + } + }); + return succeed.isTrue(); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or the traversal + * exited normally through the invocation of yield.bye(). + */ + public final void shortCircuit(LongYield yield) { + try { + this.trav.traverse(yield); + } catch (TraversableFinishError e) { + /* Proceed */ + } + } + + /** + * Returns whether no elements of this {@code LongQuery} match the provided + * {@link LongPredicate}. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code true} is returned and the predicate is not evaluated. + * + * @param p LongPredicate used to test elements of this {@code LongQuery} + */ + public boolean noneMatch(LongPredicate p) { + return !this.anyMatch(p); + } + + /** + * Returns whether any elements of this {@code LongQuery} match the provided + * {@link LongPredicate}. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the query is empty then + * {@code false} is returned and the predicate is not evaluated. + * + * @param p LongPredicate used to test elements of this {@code LongQuery} + */ + public boolean anyMatch(LongPredicate p) { + BoolBox found = new BoolBox(); + shortCircuit(item -> { + if (p.test(item)) { + found.set(); + Yield.bye(); + } + }); + return found.isTrue(); + } + + /** + * Returns an {@link OptionalLong} describing any element of this {@code LongQuery}, + * or an empty {@code OptionalLong} if this {@code LongQuery} is empty. + */ + public OptionalLong findAny() { + return this.findFirst(); + } + + /** + * Returns an {@link OptionalLong} describing the first element of this {@code LongQuery}, + * or an empty {@code OptionalLong} if this {@code LongQuery} is empty. + */ + public OptionalLong findFirst() { + LongBox box = new LongBox(); + this.tryAdvance(box::turnPresent); + return box.isPresent() + ? OptionalLong.of(box.getValue()) + : OptionalLong.empty(); + } + + /** + * Returns a {@code IntQuery} consisting of the elements of this {@code LongQuery}, + * converted to int. + *

+ * This is an intermediate operation. + */ + public IntQuery asIntQuery() { + return this.mapToInt(i -> (int) i); + } + + /** + * Returns a {@code IntQuery} consisting of the results of applying the given + * function to the elements of this {@code LongQuery}. + * + * @param function LongToIntFunction used to map the elements of this LongQuery + */ + public IntQuery mapToInt(LongToIntFunction function) { + return new IntQuery(IntAdvancer.from(adv, function), IntTraverser.from(trav, function)); + } + + /** + * Returns a {@code DoubleQuery} consisting of the elements of this {@code LongQuery}, + * converted to double. + *

+ * This is an intermediate operation. + */ + public DoubleQuery asDoubleQuery() { + return this.mapToDouble(i -> i); + } + + /** + * Returns a {@code DoubleQuery} consisting of the results of applying the given + * function to the elements of this {@code LongQuery}. + * + * @param function LongToDoubleFunction used to map the elements of this LongQuery + */ + public DoubleQuery mapToDouble(LongToDoubleFunction function) { + return new DoubleQuery(DoubleAdvancer.from(adv, function), DoubleTraverser.from(trav, function)); + } + + /** + * Returns a Stream consisting of the elements of this {@code LongQuery}, + * each boxed to an Long. + */ + public Query boxed() { + return new Query<>(adv, trav); + } + + public LongStream toStream() { + Spliterator.OfLong iter = new Spliterators.AbstractLongSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { + @Override + public boolean tryAdvance(LongConsumer action) { + return adv.tryAdvance(action::accept); + } + + @Override + public void forEachRemaining(LongConsumer action) { + trav.traverse(action::accept); + } + }; + return StreamSupport.longStream(iter, false); + } + + /** + * The {@code then} operator lets you encapsulate a piece of an operator + * chain into a function. + * That function {@code next} is applied to this {@code DoubleQuery} to produce a new + * {@code LongTraverser} object that is encapsulated in the resulting {@code DoubleQuery}. + * On the other hand, the {@code nextAdv} is applied to this query to produce a new + * {@code LongAdvancer} object that is encapsulated in the resulting query. + */ + public final LongQuery then( + Function nextAdv, + Function next) { + return new LongQuery(nextAdv.apply(this), next.apply(this)); + } + + /** + * The {@code then} operator lets you encapsulate a piece of an operator + * chain longo a function. + * That function {@code next} is applied to this {@code LongQuery} to produce a new + * {@code LongTraverser} object that is encapsulated in the resulting {@code LongQuery}. + */ + public final LongQuery then(Function next) { + LongAdvancer nextAdv = item -> { + throw new UnsupportedOperationException( + "Missing tryAdvance() implementation! Use the overloaded then() providing both Advancer and Traverser!"); + }; + return new LongQuery(nextAdv, next.apply(this)); + } + + /** + * Returns a {@code LongQuery} consisting of the longest prefix of elements taken from + * this {@code LongQuery} that match the given predicate. + */ + public final LongQuery takeWhile(LongPredicate predicate) { + LongTakeWhile take = new LongTakeWhile(this, predicate); + return new LongQuery(take, take); + } + + /** + * Creates a concatenated {@code Query} in which the elements are + * all the elements of this {@code Query} followed by all the + * elements of the other {@code Query}. + */ + public final LongQuery concat(LongQuery other) { + LongConcat cat = new LongConcat(this, other); + return new LongQuery(cat, cat); + } + + /** + * Returns a {@code LongQuery} consisting of the remaining elements of this query + * after discarding the first sequence of elements that match the given Predicate. + */ + public final LongQuery dropWhile(LongPredicate predicate) { + LongDropWhile drop = new LongDropWhile(this, predicate); + return new LongQuery(drop, drop); + } + + /** + * Applies a specified function to the corresponding elements of two + * sequences, producing a sequence of the results. + */ + public final LongQuery zip(LongQuery other, LongBinaryOperator zipper) { + LongZip zip = new LongZip(this, other, zipper); + return new LongQuery(zip, zip); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/LongTraverser.java b/src/main/java/org/xbib/event/yield/primitives/lng/LongTraverser.java new file mode 100644 index 0000000..a2b14d4 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/LongTraverser.java @@ -0,0 +1,74 @@ +package org.xbib.event.yield.primitives.lng; + +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.dbl.DoubleTraverser; +import org.xbib.event.yield.primitives.intgr.IntAdvancer; +import org.xbib.event.yield.primitives.intgr.IntTraverser; + +import java.util.function.DoubleToLongFunction; +import java.util.function.IntToLongFunction; +import java.util.function.ToLongFunction; + +/** + * Bulk traversal. + * We use traverse method as its first choice to + * implement Query operations. + * This is a special kind of traversal that disallows individually access. + */ +public interface LongTraverser extends Traverser { + /** + * An LongTraverser object without elements. + */ + static LongTraverser empty() { + return yield -> { + }; + } + + /** + * An LongTraverser object from a generic {@link Traverser} mapped by a {@link ToLongFunction}. + * + * @param source {@link Traverser} with the source elements for this {@code LongTraverser}. + * @param mapper {@link ToLongFunction} that specifies how to map the source elements longo long values. + */ + static LongTraverser from(Traverser source, ToLongFunction mapper) { + return yield -> source.traverse(item -> yield.ret(mapper.applyAsLong(item))); + } + + /** + * An LongTraverser object from a {@link DoubleTraverser} mapped by a {@link DoubleToLongFunction}. + * + * @param source {@link DoubleTraverser} with the source elements for this {@code LongAdvancer}. + * @param mapper {@link DoubleToLongFunction} that specifies how to map the source elements longo long values. + */ + static LongTraverser from(DoubleTraverser source, DoubleToLongFunction mapper) { + return from((Traverser) source, mapper::applyAsLong); + } + + /** + * An LongAdvancer object from a {@link IntTraverser} mapped by a {@link IntToLongFunction}. + * + * @param source {@link IntAdvancer} with the source elements for this {@code LongAdvancer}. + * @param mapper {@link IntToLongFunction} that specifies how to map the source elements longo long values. + */ + static LongTraverser from(IntTraverser source, IntToLongFunction mapper) { + return from((Traverser) source, mapper::applyAsLong); + } + + /** + * Default traverse implementation that calls the + * primitive version of it + */ + @Override + default void traverse(Yield yield) { + LongYield yld = yield::ret; + this.traverse(yld); + } + + /** + * Yields elements sequentially in the current thread, + * until all elements have been processed or an + * exception is thrown. + */ + void traverse(LongYield yield); +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/LongYield.java b/src/main/java/org/xbib/event/yield/primitives/lng/LongYield.java new file mode 100644 index 0000000..6288075 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/LongYield.java @@ -0,0 +1,16 @@ +package org.xbib.event.yield.primitives.lng; + +import org.xbib.event.yield.Yield; + +/** + * Equivalent to {@code LongConsumer} with a yield semantics. + */ +@FunctionalInterface +public interface LongYield extends Yield { + void ret(long item); + + @Override + default void ret(Long item) { + this.ret((long) item); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/FromLongArray.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/FromLongArray.java new file mode 100644 index 0000000..e45a7fd --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/FromLongArray.java @@ -0,0 +1,41 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +public class FromLongArray implements LongAdvancer, LongTraverser { + + private final long[] data; + + private int current; + + public FromLongArray(long... data) { + this.data = data; + this.current = 0; + } + + public boolean hasNext() { + return current < data.length; + } + + /** + * Continues from the polong where tryAdvance or next left the + * longernal iteration. + * + * @param yield yield + */ + @Override + public void traverse(LongYield yield) { + for (int i = current; i < data.length; i++) { + yield.ret(data[i]); + } + } + + @Override + public boolean tryAdvance(LongYield yield) { + if (!hasNext()) return false; + yield.ret(data[current++]); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/FromLongStream.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/FromLongStream.java new file mode 100644 index 0000000..0e87757 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/FromLongStream.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.Spliterator; +import java.util.function.LongConsumer; +import java.util.stream.LongStream; + +public class FromLongStream implements LongAdvancer, LongTraverser { + private final Spliterator.OfLong upstream; + + public FromLongStream(LongStream data) { + this.upstream = data.spliterator(); + } + + @Override + public void traverse(LongYield yield) { + LongConsumer cons = yield::ret; + upstream.forEachRemaining(cons); + } + + @Override + public boolean tryAdvance(LongYield yield) { + LongConsumer cons = yield::ret; + return upstream.tryAdvance(cons); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongConcat.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongConcat.java new file mode 100644 index 0000000..478b102 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongConcat.java @@ -0,0 +1,27 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +public class LongConcat implements LongAdvancer, LongTraverser { + private final LongQuery first; + private final LongQuery second; + + public LongConcat(LongQuery first, LongQuery second) { + this.first = first; + this.second = second; + } + + @Override + public void traverse(LongYield yield) { + this.first.traverse(yield); + this.second.traverse(yield); + } + + @Override + public boolean tryAdvance(LongYield yield) { + return first.tryAdvance(yield) || second.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongDistinct.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongDistinct.java new file mode 100644 index 0000000..83dbd76 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongDistinct.java @@ -0,0 +1,39 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.HashSet; + +public class LongDistinct implements LongAdvancer, LongTraverser { + final HashSet mem = new HashSet<>(); + private final LongQuery upstream; + + public LongDistinct(LongQuery adv) { + this.upstream = adv; + } + + @Override + public void traverse(LongYield yield) { + upstream.traverse(item -> { + if (mem.add(item)) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(LongYield yield) { + final BoolBox found = new BoolBox(); + while (found.isFalse() && upstream.tryAdvance(item -> { + if (mem.add(item)) { + yield.ret(item); + found.set(); + } + })) ; + return found.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongDropWhile.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongDropWhile.java new file mode 100644 index 0000000..7293fd2 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongDropWhile.java @@ -0,0 +1,54 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.function.LongPredicate; + +public class LongDropWhile implements LongAdvancer, LongTraverser { + + private final LongQuery upstream; + private final LongPredicate predicate; + private boolean dropped; + + public LongDropWhile(LongQuery upstream, LongPredicate predicate) { + this.upstream = upstream; + this.predicate = predicate; + this.dropped = false; + } + + @Override + public void traverse(LongYield yield) { + upstream.traverse(item -> { + if (!dropped && !predicate.test(item)) { + dropped = true; + } + if (dropped) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(LongYield yield) { + if (dropped) { + return upstream.tryAdvance(yield); + } else { + while (!dropped && dropNext(yield)) { + // Intentionally empty. Action specified on yield statement of tryAdvance(). + } + return dropped; + } + } + + private boolean dropNext(LongYield yield) { + return upstream.tryAdvance(item -> { + if (!predicate.test(item)) { + dropped = true; + yield.ret(item); + } + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongFilter.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongFilter.java new file mode 100644 index 0000000..828ccdd --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongFilter.java @@ -0,0 +1,43 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.function.LongPredicate; + +public class LongFilter implements LongAdvancer, LongTraverser { + private final LongQuery upstream; + private final LongPredicate p; + + public LongFilter(LongQuery adv, LongPredicate p) { + this.upstream = adv; + this.p = p; + } + + @Override + public void traverse(LongYield yield) { + upstream.traverse(e -> { + if (p.test(e)) { + yield.ret(e); + } + }); + } + + @Override + public boolean tryAdvance(LongYield yield) { + BoolBox found = new BoolBox(); + while (found.isFalse()) { + boolean hasNext = upstream.tryAdvance(item -> { + if (p.test(item)) { + yield.ret(item); + found.set(); + } + }); + if (!hasNext) break; + } + return found.isTrue(); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongFlatMap.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongFlatMap.java new file mode 100644 index 0000000..45e7d5f --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongFlatMap.java @@ -0,0 +1,35 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.function.LongFunction; + +public class LongFlatMap implements LongAdvancer, LongTraverser { + private final LongQuery upstream; + private final LongFunction mapper; + LongQuery src; + + public LongFlatMap(LongQuery query, LongFunction mapper) { + this.upstream = query; + this.mapper = mapper; + src = new LongQuery(LongAdvancer.empty(), LongTraverser.empty()); + } + + @Override + public void traverse(LongYield yield) { + upstream.traverse(elem -> mapper.apply(elem).traverse(yield)); + + } + + @Override + public boolean tryAdvance(LongYield yield) { + while (!src.tryAdvance(yield)) { + if (!upstream.tryAdvance(t -> src = mapper.apply(t))) + return false; + } + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongGenerate.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongGenerate.java new file mode 100644 index 0000000..f6162c6 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongGenerate.java @@ -0,0 +1,28 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.function.LongSupplier; + +public class LongGenerate implements LongAdvancer, LongTraverser { + private final LongSupplier s; + + public LongGenerate(LongSupplier s) { + this.s = s; + } + + @Override + public void traverse(LongYield yield) { + while (true) { + yield.ret(s.getAsLong()); + } + } + + @Override + public boolean tryAdvance(LongYield yield) { + yield.ret(s.getAsLong()); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongIterate.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongIterate.java new file mode 100644 index 0000000..5ffb6de --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongIterate.java @@ -0,0 +1,38 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.function.LongUnaryOperator; + +public class LongIterate implements LongAdvancer, LongTraverser { + private final LongUnaryOperator f; + private long prev; + + public LongIterate(long seed, LongUnaryOperator f) { + this.f = f; + this.prev = seed; + } + + /** + * Continues from the polong where tryAdvance or next left the + * longernal iteration. + * + * @param yield + */ + @Override + public void traverse(LongYield yield) { + for (long i = prev; true; i = f.applyAsLong(i)) { + yield.ret(i); + } + } + + @Override + public boolean tryAdvance(LongYield yield) { + long curr = prev; + prev = f.applyAsLong(prev); + yield.ret(curr); + return true; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongLimit.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongLimit.java new file mode 100644 index 0000000..fc9619d --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongLimit.java @@ -0,0 +1,34 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +public class LongLimit implements LongAdvancer, LongTraverser { + private final LongQuery upstream; + private final int n; + int count; + + public LongLimit(LongQuery upstream, int n) { + this.upstream = upstream; + this.n = n; + count = 0; + } + + @Override + public void traverse(LongYield yield) { + if (count >= n) + throw new IllegalStateException("Traverser has already been operated on or closed!"); + while (this.tryAdvance(yield)) { + // Intentionally empty. Action specified on yield statement of tryAdvance(). + } + } + + @Override + public boolean tryAdvance(LongYield yield) { + if (count >= n) return false; + count++; + return upstream.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongMap.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongMap.java new file mode 100644 index 0000000..73d34dc --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongMap.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.function.LongUnaryOperator; + +public class LongMap implements LongAdvancer, LongTraverser { + + private final LongQuery upstream; + private final LongUnaryOperator mapper; + + public LongMap(LongQuery adv, LongUnaryOperator mapper) { + this.upstream = adv; + this.mapper = mapper; + } + + @Override + public void traverse(LongYield yield) { + upstream.traverse(e -> yield.ret(mapper.applyAsLong(e))); + } + + @Override + public boolean tryAdvance(LongYield yield) { + return upstream.tryAdvance(item -> yield.ret(mapper.applyAsLong(item))); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongMapToObj.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongMapToObj.java new file mode 100644 index 0000000..b10fddf --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongMapToObj.java @@ -0,0 +1,29 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.Advancer; +import org.xbib.event.yield.Traverser; +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.lng.LongQuery; + +import java.util.function.LongFunction; + +public class LongMapToObj implements Advancer, Traverser { + + private final LongQuery upstream; + private final LongFunction mapper; + + public LongMapToObj(LongQuery adv, LongFunction mapper) { + this.upstream = adv; + this.mapper = mapper; + } + + @Override + public void traverse(Yield yield) { + upstream.traverse(e -> yield.ret(mapper.apply(e))); + } + + @Override + public boolean tryAdvance(Yield yield) { + return upstream.tryAdvance(item -> yield.ret(mapper.apply(item))); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongPeek.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongPeek.java new file mode 100644 index 0000000..1fc0636 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongPeek.java @@ -0,0 +1,34 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.function.LongConsumer; + +public class LongPeek implements LongAdvancer, LongTraverser { + private final LongQuery upstream; + private final LongConsumer action; + + public LongPeek(LongQuery adv, LongConsumer action) { + this.upstream = adv; + this.action = action; + } + + @Override + public void traverse(LongYield yield) { + upstream.traverse(item -> { + action.accept(item); + yield.ret(item); + }); + } + + @Override + public boolean tryAdvance(LongYield yield) { + return upstream.tryAdvance(item -> { + action.accept(item); + yield.ret(item); + }); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongSkip.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongSkip.java new file mode 100644 index 0000000..a40e3e9 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongSkip.java @@ -0,0 +1,41 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +public class LongSkip implements LongAdvancer, LongTraverser { + private final LongQuery upstream; + private final int n; + int index; + + public LongSkip(LongQuery adv, int n) { + this.upstream = adv; + this.n = n; + index = 0; + } + + /** + * Continues from the polong where tryAdvance or next left the + * longernal iteration. + * + * @param yield + */ + @Override + public void traverse(LongYield yield) { + upstream.traverse(item -> { + if (index++ >= n) { + yield.ret(item); + } + }); + } + + @Override + public boolean tryAdvance(LongYield yield) { + for (; index < n; index++) + upstream.tryAdvance(item -> { + }); + return upstream.tryAdvance(yield); + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongTakeWhile.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongTakeWhile.java new file mode 100644 index 0000000..6e776f8 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongTakeWhile.java @@ -0,0 +1,44 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.function.LongPredicate; + +public class LongTakeWhile implements LongAdvancer, LongTraverser { + private final LongQuery upstream; + private final LongPredicate predicate; + private boolean hasNext; + + public LongTakeWhile(LongQuery upstream, LongPredicate predicate) { + this.upstream = upstream; + this.predicate = predicate; + this.hasNext = true; + } + + @Override + public void traverse(LongYield yield) { + upstream.shortCircuit(item -> { + if (!predicate.test(item)) { + Yield.bye(); + } + yield.ret(item); + }); + } + + @Override + public boolean tryAdvance(LongYield yield) { + if (!hasNext) return false; // Once predicate is false it finishes the iteration + LongYield takeWhile = item -> { + if (predicate.test(item)) { + yield.ret(item); + } else { + hasNext = false; + } + }; + return upstream.tryAdvance(takeWhile) && hasNext; + } +} diff --git a/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongZip.java b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongZip.java new file mode 100644 index 0000000..6e540f5 --- /dev/null +++ b/src/main/java/org/xbib/event/yield/primitives/lng/ops/LongZip.java @@ -0,0 +1,40 @@ +package org.xbib.event.yield.primitives.lng.ops; + +import org.xbib.event.yield.Yield; +import org.xbib.event.yield.boxes.BoolBox; +import org.xbib.event.yield.primitives.lng.LongAdvancer; +import org.xbib.event.yield.primitives.lng.LongQuery; +import org.xbib.event.yield.primitives.lng.LongTraverser; +import org.xbib.event.yield.primitives.lng.LongYield; + +import java.util.function.LongBinaryOperator; + +public class LongZip implements LongAdvancer, LongTraverser { + private final LongQuery upstream; + private final LongQuery other; + private final LongBinaryOperator zipper; + + public LongZip(LongQuery upstream, LongQuery other, LongBinaryOperator zipper) { + this.upstream = upstream; + this.other = other; + this.zipper = zipper; + } + + @Override + public void traverse(LongYield yield) { + upstream.shortCircuit(e1 -> { + if (!other.tryAdvance(e2 -> yield.ret(zipper.applyAsLong(e1, e2)))) + Yield.bye(); + }); + } + + @Override + public boolean tryAdvance(LongYield yield) { + BoolBox consumed = new BoolBox(); + upstream.tryAdvance(e1 -> other.tryAdvance(e2 -> { + yield.ret(zipper.applyAsLong(e1, e2)); + consumed.set(); + })); + return consumed.isTrue(); + } +} diff --git a/src/test/java/org/xbib/event/async/test/AsyncFileReaderTest.java b/src/test/java/org/xbib/event/async/test/AsyncFileReaderTest.java new file mode 100644 index 0000000..4840f7c --- /dev/null +++ b/src/test/java/org/xbib/event/async/test/AsyncFileReaderTest.java @@ -0,0 +1,400 @@ +package org.xbib.event.async.test; + +import io.reactivex.rxjava3.core.Observable; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import org.xbib.event.async.AsyncFiles; +import org.xbib.event.yield.AsyncQuery; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static io.reactivex.rxjava3.core.Observable.fromArray; +import static java.lang.System.lineSeparator; +import static java.lang.System.out; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.Files.delete; +import static java.util.Arrays.asList; +import static java.util.Collections.max; +import static java.util.Comparator.comparingInt; +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.xbib.event.async.Subscribers.doOnNext; + +public class AsyncFileReaderTest { + + private static final Pattern NEWLINE = Pattern.compile("(\r\n|\n|\r)"); + + private static final String OUTPUT = "build/output.txt"; + + private static final Path METAMORPHOSIS = getPath("Metamorphosis-by-Franz-Kafka.txt"); + + private static final Path WIZARD = getPath("The-Wizard-by-Rider-Haggard.txt"); + + private static Path getPath(String resource) { + try { + return Paths.get(AsyncFileReaderTest.class.getResource("/" + resource).toURI()); + } catch (NullPointerException | URISyntaxException e) { + e.printStackTrace(); + throw new IllegalStateException(); + } + } + + public void readLinesAndPrint() throws IOException { + List expected = asList("super", "brave", "isel", "ole", "gain", "massi", "tot"); + Path path = Paths.get(OUTPUT); + Files.write(path, expected); + CompletableFuture cf = new CompletableFuture<>(); + AsyncFiles + .lines(path) + .subscribe(doOnNext(out::println) + .doOnComplete(() -> cf.complete(null)) + .doOnSubscribe(sign -> sign.request(Long.MAX_VALUE))); + cf.join(); + } + + public void asynQueryLinesAndPrint() throws IOException { + List expected = asList("super", "brave", "isel", "ole", "gain", "massi", "tot"); + Path path = Paths.get(OUTPUT); + Files.write(path, expected); + AsyncFiles + .asyncQuery(path) + .subscribe((line, err) -> out.println(line)) + .join(); + } + + @Test + public void readLinesWith4BytesBuffer() throws IOException { + final List expected = asList("super", "brave", "isel", "ole", "gain", "massi", "tot"); + try (FileWriter out = new FileWriter(OUTPUT)) { + expected.forEach(item -> writeLine(out, item)); + out.flush(); + } + try { + CompletableFuture p = new CompletableFuture<>(); + Iterator iter = expected.iterator(); + AsyncFiles + .lines(4, OUTPUT) + .subscribe(doOnNext(item -> { + if (p.isDone()) return; + String curr = iter.next(); + assertEquals(curr, item); + }) + .doOnSubscribe(sign -> sign.request(Integer.MAX_VALUE)) + .doOnError(p::completeExceptionally) + .doOnComplete(() -> p.complete(null))); + p.join(); + assertFalse(iter.hasNext(), "Missing items not retrieved by lines subscriber!!"); + } finally { + delete(Paths.get(OUTPUT)); + } + } + + private void writeLine(FileWriter out, String item) { + try { + out.write(item + '\n'); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Test + public void readAllBytesWith8BytesBuffer() throws IOException { + final List expected = asList("super", "brave", "isel", "ole", "gain", "massi", "tot"); + Files.write(Paths.get(OUTPUT), expected); + try { + AsyncFiles + .readAll(OUTPUT, 8) + .thenApply(NEWLINE::splitAsStream) + .thenApply(Stream::iterator) + .thenAccept(actual -> expected.forEach(l -> { + if (!actual.hasNext()) { + fail("File does not contain line: " + l); + } + String curr = actual.next(); + assertEquals(l, curr); + })) + .join(); + } finally { + delete(Paths.get(OUTPUT)); + } + } + + @Test + public void readAllBytesFromLargeFile() throws IOException { + Path path = METAMORPHOSIS; + Iterator expected = Files.lines(path, UTF_8).iterator(); + AsyncFiles + .readAll(path.toString()) // KEEP like this with toString() to force invocation chain cover more methods + .thenAccept(actual -> NEWLINE + .splitAsStream(actual) + .forEach(line -> { + if (!expected.hasNext()) + fail("More items read than expected!"); + assertEquals(expected.next(), line); + })) + .join(); + if (expected.hasNext()) + fail("There are missing lines to read: " + expected.next()); + + } + + @Test + public void readLinesFromLargeFile() throws IOException { + Path PATH = WIZARD; + Iterator expected = Files.lines(PATH, UTF_8).iterator(); + CompletableFuture completed = new CompletableFuture<>(); + AsyncFiles + .lines(PATH.toString()) + .subscribe(doOnNext(item -> { + if (completed.isDone()) return; + String curr = expected.next(); + assertEquals(curr, item); + }) + .doOnSubscribe(sign -> sign.request(Integer.MAX_VALUE)) + .doOnError(completed::completeExceptionally) + .doOnComplete(() -> completed.complete(null))); + completed.join(); + assertFalse(expected.hasNext(), "Missing items not retrieved by lines subscriber!!"); + } + + @Test + public void readLinesFromLargeFileAndSleep() throws IOException, InterruptedException { + Path path = WIZARD; + Iterator expected = Files.lines(path, UTF_8).iterator(); + CompletableFuture completed = new CompletableFuture<>(); + CompletableFuture sign = new CompletableFuture<>(); + AsyncFiles + .lines(path.toString()) + .subscribe(doOnNext(item -> { + if (completed.isDone()) return; + String curr = expected.next(); + assertEquals(curr, item); + }).doOnSubscribe(s -> { + s.request(400); + sign.complete(s); + }).doOnError(completed::completeExceptionally) + .doOnComplete(() -> completed.complete(null))); + Thread.sleep(1000); + sign.join().request(Long.MAX_VALUE); + completed.join(); + assertFalse(expected.hasNext(), "Missing items not retrieved by lines subscriber!!"); + } + + @Test + public void readLinesFromLargeFileAndCancelation() throws InterruptedException { + CompletableFuture completed = new CompletableFuture<>(); + CompletableFuture subscribed = new CompletableFuture<>(); + int[] count = {0}; + AsyncFiles + .lines(64, WIZARD) + .subscribe(doOnNext(item -> { + count[0]++; + if (count[0] > 400) { + subscribed.join().cancel(); + completed.complete(null); + } + }) + .doOnSubscribe(s -> { + s.request(Integer.MAX_VALUE); + subscribed.complete(s); + }) + .doOnError(completed::completeExceptionally)); + completed.join(); + Thread.sleep(500); // Wait a little to check that no further signals were emitted in the meanwhile. + assertEquals(count[0], 401); + } + + @Test + public void readLinesFromLargeFileRequestRandomly() throws IOException { + Path PATH = WIZARD; + Iterator expected = Files.lines(PATH, UTF_8).iterator(); + Random rand = new Random(); + CompletableFuture completed = new CompletableFuture<>(); + Subscription[] sign = {null}; + AtomicInteger signals = new AtomicInteger(); + AtomicInteger prevRequest = new AtomicInteger(); + AsyncFiles + .lines(PATH.toString()) + .subscribe(doOnNext(item -> { + if (completed.isDone()) return; + String curr = expected.next(); + assertEquals(curr, item); + if (prevRequest.decrementAndGet() == 0) { + int req = rand.nextInt(50) + 1; + sign[0].request(req); + prevRequest.addAndGet(req); + signals.incrementAndGet(); + } + }) + .doOnSubscribe(s -> { + int req = rand.nextInt(50) + 1; + s.request(req); + prevRequest.addAndGet(req); + sign[0] = s; + }) + .doOnError(completed::completeExceptionally) + .doOnComplete(() -> completed.complete(null))); + completed.join(); + assertFalse(expected.hasNext(), "Missing items not retrieved by lines subscriber!!"); + assertTrue(signals.get() < 2000); + } + + @Test + public void readAllBytesFromLargeFileViaCallbackConcurrently() throws IOException { + Path path = METAMORPHOSIS; + String expected = Files + .lines(path, UTF_8) + .map(line -> line + lineSeparator()) + .collect(joining()); + CompletableFuture p1 = new CompletableFuture<>(); + CompletableFuture p2 = new CompletableFuture<>(); + AsyncFiles + .readAll(path.toString(), (err, actual) -> { + assertEquals(expected, actual); + p1.complete(null); + }); + AsyncFiles + .readAll(path.toString(), (err, actual) -> { + assertEquals(expected, actual); + p2.complete(null); + }); + p1.join(); + p2.join(); + } + + @Test + public void readLinesFromLargeFileRxJavaObservable() throws IOException { + Path PATH = METAMORPHOSIS; + Iterator expected = Files.lines(PATH, UTF_8).iterator(); + Observable + .fromPublisher(AsyncFiles.lines(PATH)) + .doOnError(ex -> fail(ex.getMessage())) + .doOnNext(line -> { + if (!expected.hasNext()) { + fail("More items read than expected!"); + } + String next = expected.next(); + assertEquals(next, line); + }) + .blockingSubscribe(); + if (expected.hasNext()) + fail("There are missing lines to read: " + expected.next()); + } + + @Test + public void readLinesFromLargeFileRxJavaObservableAndCountWords() { + final int MIN = 5; + final int MAX = 10; + ConcurrentHashMap words = new ConcurrentHashMap<>(); + Publisher lines = AsyncFiles.lines(WIZARD); + Observable + .fromPublisher(lines) + .filter(line -> !line.isEmpty()) + .skip(14) + .takeWhile(line -> !line.contains("*** END OF ")) + .flatMap(line -> fromArray(line.split(" "))) + .filter(word -> word.length() > MIN && word.length() < MAX) + .doOnNext(w -> words.merge(w, 1, Integer::sum)) + .blockingSubscribe(); + Map.Entry common = max(words.entrySet(), comparingInt(Map.Entry::getValue)); + assertEquals(common.getKey(), "Hokosa"); + assertEquals(common.getValue().intValue(), 183); + } + + /** + * Trying to reproduce an erroneous behavior where takeWhile tries + * to terminate upstream subscription before it exist. + */ + @Test + public void readLinesAndImmediatelyInterrupt() { + AsyncFiles + .asyncQuery(WIZARD) + .filter(line -> !line.isEmpty()) + .takeWhile(line -> false) // Will finish on first line + .blockingSubscribe(); + } + + @Test + public void readLinesFromLargeFileAsyncQueryAndCountWords() { + final int MIN = 5; + final int MAX = 10; + ConcurrentHashMap words = new ConcurrentHashMap<>(); + AsyncFiles + .asyncQuery(WIZARD) + .filter(line -> !line.isEmpty()) + .skip(14) + .takeWhile(line -> !line.contains("*** END OF ")) + .flatMapMerge(line -> AsyncQuery.of(line.split(" "))) + .filter(word -> word.length() > MIN && word.length() < MAX) + .onNext((w, err) -> words.merge(w, 1, Integer::sum)) + .blockingSubscribe(); + Map.Entry common = max(words.entrySet(), comparingInt(Map.Entry::getValue)); + assertEquals(common.getKey(), "Hokosa"); + assertEquals(common.getValue().intValue(), 183); + } + + @Test + public void readLinesFromLargeFileAsyncQueryAndCountDistinctWords() { + int[] count = {0}; + AsyncFiles + .asyncQuery(WIZARD) + .filter(line -> !line.isEmpty()) + .skip(14) + .takeWhile(line -> !line.contains("*** END OF ")) + .flatMapMerge(line -> AsyncQuery.of(line.split("\\W+"))) + .distinct() + .onNext((word, err) -> { + if (err == null) count[0]++; + }) + .onNext((word, err) -> { + if (err != null) { + err.printStackTrace(); + } + }) + .blockingSubscribe(); + assertEquals(count[0], 5206); + } + + + /** + * Despite this test being very similar to the previous one it suppresses the takeWhile() + * operation from the pipeline. + * The takeWhile() was hiding a bug in asyncQuery() on completion of the CF. + */ + @Test + public void readLinesFromLargeFileAsyncQueryWithoutTakeWhile() { + final int MIN = 5; + final int MAX = 10; + ConcurrentHashMap words = new ConcurrentHashMap<>(); + AsyncFiles + .asyncQuery((WIZARD)) + .filter(line -> !line.isEmpty()) + .skip(14) + .flatMapMerge(line -> AsyncQuery.of(line.split(" "))) + .filter(word -> word.length() > MIN && word.length() < MAX) + .onNext((w, err) -> words.merge(w, 1, Integer::sum)) + .blockingSubscribe(); + Map.Entry common = max(words.entrySet(), comparingInt(Map.Entry::getValue)); + assertEquals(common.getKey(), "Hokosa"); + assertEquals(common.getValue().intValue(), 183); + } +} \ No newline at end of file diff --git a/src/test/java/org/xbib/event/async/test/AsyncFileWriterTest.java b/src/test/java/org/xbib/event/async/test/AsyncFileWriterTest.java new file mode 100644 index 0000000..925b112 --- /dev/null +++ b/src/test/java/org/xbib/event/async/test/AsyncFileWriterTest.java @@ -0,0 +1,108 @@ +package org.xbib.event.async.test; + +import org.junit.jupiter.api.Test; +import org.xbib.event.async.AsyncFiles; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import static java.nio.file.Files.delete; +import static java.nio.file.Files.lines; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class AsyncFileWriterTest { + + @Test + public void writeLinesTest() throws IOException { + final String path = "build/output1.txt"; + final List expected = Arrays.asList("super", "brave", "isel", "ole", "gain", "massive"); + try { + AsyncFiles.write(Paths.get(path), expected) + .whenComplete((index, ex) -> { + if (ex != null) { + fail(ex.getMessage()); + } + }) + .join(); + Iterator actual = lines(Paths.get(path)).iterator(); + if (!actual.hasNext()) { + fail("File is empty!!!"); + } + expected.forEach(l -> { + if (actual.hasNext()) { + assertEquals(l, actual.next()); + } + else { + fail("File does not contain line: " + l); + } + }); + } finally { + delete(Paths.get(path)); + } + } + + @Test + public void writeBytesTest() throws IOException, URISyntaxException { + final String output = "build/output2.txt"; + URL resource = getClass().getResource("/Metamorphosis-by-Franz-Kafka.txt"); + Path path = Paths.get(resource.toURI()); + byte[] expected = Files.readAllBytes(path); + AsyncFiles.writeBytes(Paths.get(output), expected) + .join(); + try { + AsyncFiles + .readAllBytes(Paths.get(output)) + .whenComplete((actual, ex) -> { + if (ex != null) { + fail(ex.getMessage()); + } + assertArrayEquals(expected, actual); + }) + .join(); + } finally { + delete(Paths.get(output)); + } + } + + @Test + public void readWriteTestAsyncForReadme() throws IOException, URISyntaxException { + URL resource = getClass().getResource("/Metamorphosis-by-Franz-Kafka.txt"); + Path in = Paths.get(resource.toURI()); + Path out = Paths.get("build/output3.txt"); + try { + AsyncFiles.readAllBytes(in) + .thenCompose(bytes -> AsyncFiles.writeBytes(out, bytes)) + .join(); + byte[] expected = Files.readAllBytes(in); + byte[] actual = Files.readAllBytes(out); + assertArrayEquals(expected, actual); + } finally { + delete(out); + } + } + + @Test + public void readWriteTestSyncForReadme() throws IOException, URISyntaxException { + URL resource = getClass().getResource("/Metamorphosis-by-Franz-Kafka.txt"); + Path in = Paths.get(resource.toURI()); + Path out = Paths.get("build/output4.txt"); + try { + byte[] data = Files.readAllBytes(in); + Files.write(out, data); + byte[] expected = Files.readAllBytes(in); + byte[] actual = Files.readAllBytes(out); + assertArrayEquals(expected, actual); + } finally { + delete(out); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/xbib/event/async/test/AsyncFilesFailures.java b/src/test/java/org/xbib/event/async/test/AsyncFilesFailures.java new file mode 100644 index 0000000..912956b --- /dev/null +++ b/src/test/java/org/xbib/event/async/test/AsyncFilesFailures.java @@ -0,0 +1,201 @@ +package org.xbib.event.async.test; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.xbib.event.async.AsyncFiles; +import org.xbib.event.async.Subscribers; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.stream.Stream; + +import static java.nio.channels.AsynchronousFileChannel.open; +import static java.nio.file.Files.delete; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class AsyncFilesFailures { + + private static final Path METAMORPHOSIS = getPath("Metamorphosis-by-Franz-Kafka.txt"); + + private static final Path UTF_8_INVALID = getPath("UTF-8-test.txt"); + + private static Path getPath(String resource) { + try { + return Paths.get(AsyncFileReaderTest.class.getResource("/" + resource).toURI()); + } catch (NullPointerException | URISyntaxException e) { + e.printStackTrace(); + throw new IllegalStateException(); + } + } + + private static void openLock(Path output) { + try { + open(output, StandardOpenOption.WRITE).lock().get(); + } catch (InterruptedException | IOException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Test + public void readAllNoSuchFile() { + AsyncFiles.readAllBytes(Paths.get("non-existent-file.sgf")) + .whenComplete((pos, err) -> { + if (err == null) + fail("It should fail reading an nonexistent!"); + else + assertEquals(err.getClass(), NoSuchFileException.class); + }); + } + + @Test + public void readAllLinesNoSuchFile() { + AsyncFiles.lines("non-existent-file.sgf") + .subscribe(Subscribers + .doOnNext(line -> + fail("It should fail reading an nonexistent!") + ) + .doOnError(err -> + assertEquals(err.getClass(), NoSuchFileException.class) + )); + } + + @Test + public void readAllToCallbackNoSuchFile() { + AsyncFiles.readAll("non-existent-file.sgf", (err, data) -> { + if (err == null || data != null) { + fail("It should fail reading an nonexistent!"); + } else { + assertEquals(err.getClass(), NoSuchFileException.class); + } + }); + } + + @Disabled + @Test + public void concurrentReadLines() throws IOException { + final Path path = Paths.get("build/dummy1.txt"); + List expected = asList("super", "brave", "isel", "ole", "gain", "massi", "tot"); + Files.write(path, expected, StandardOpenOption.CREATE); + try { + CompletableFuture promise = new CompletableFuture<>(); + boolean[] done = {false}; + AsyncFiles.lines(8, path) + .subscribe(Subscribers + .doOnNext(item -> { + if (!done[0]) { + openLock(path); + done[0] = true; + } + }) + .doOnSubscribe(sign -> sign.request(Integer.MAX_VALUE)) + .doOnError(err -> { + assertTrue(err instanceof IOException); + promise.complete(null); + })); + promise.join(); + } finally { + Files.delete(path); + } + } + + @Test + public void concurrentReadBytes() { + Path path = METAMORPHOSIS; + CompletableFuture p = AsyncFiles + .readAllBytes(path, 8) + .whenComplete((arr, err) -> { + assertNull(arr); + assertTrue(err instanceof CompletionException); + }); + openLock(path); + try { + p.join(); + fail("It should be completed exceptionally!"); + } catch (CompletionException e) { + // + } + } + + @Disabled + @Test + public void concurrentWriteLines() throws IOException { + final Path path = Paths.get("build/dummy2.txt"); + Stream lines = Files.lines(METAMORPHOSIS); + Iterable expected = lines::iterator; + try { + CompletableFuture p = AsyncFiles.write(path, expected); + openLock(path); + p.join(); + fail("It should be completed exceptionally!"); + } catch (CompletionException e) { + /* Assert true */ + } finally { + delete(path); + } + } + + @Test + public void concurrentBytesLinesOnOpen() throws IOException { + final Path path = Paths.get("build/dummy3.txt"); + Files.write(path, "ola".getBytes(), StandardOpenOption.CREATE); + try { + AsyncFiles.writeBytes(path, null) + .whenComplete((pos, err) -> { + if (err == null) { + fail("It should fail creating a file that already exists"); + } else { + assertEquals(err.getClass(), IOException.class); + } + }); + } finally { + delete(path); + } + } + + @Test + public void concurrentWriteLinesOnOpen() throws IOException { + final Path path = Paths.get("build/dummy4.txt"); + Files.write(path, "ola".getBytes(), StandardOpenOption.CREATE); + try { + AsyncFiles.write(path, null) + .whenComplete((pos, err) -> { + if (err == null) + fail("It should fail creating a file that already exists"); + else + assertEquals(err.getClass(), IOException.class); + }); + } finally { + delete(path); + } + } + + @Test + public void readLinesWithInvalidBytes() { + CompletableFuture p = new CompletableFuture<>(); + AsyncFiles + .lines(4, UTF_8_INVALID) + .subscribe(Subscribers + .doOnNext((item) -> { + }) + .doOnSubscribe(sign -> sign.request(Integer.MAX_VALUE)) + .doOnError(err -> fail()) + .doOnComplete(() -> + p.complete(null) + ) + ); + p.join(); + } +} diff --git a/src/test/resources/Metamorphosis-by-Franz-Kafka.txt b/src/test/resources/Metamorphosis-by-Franz-Kafka.txt new file mode 100644 index 0000000..acf5b8f --- /dev/null +++ b/src/test/resources/Metamorphosis-by-Franz-Kafka.txt @@ -0,0 +1,2362 @@ +The Project Gutenberg EBook of Metamorphosis, by Franz Kafka +Translated by David Wyllie. + +This eBook is for the use of anyone anywhere at no cost and with +almost no restrictions whatsoever. You may copy it, give it away or +re-use it under the terms of the Project Gutenberg License included +with this eBook or online at www.gutenberg.net + +** This is a COPYRIGHTED Project Gutenberg eBook, Details Below ** +** Please follow the copyright guidelines in this file. ** + + +Title: Metamorphosis + +Author: Franz Kafka + +Translator: David Wyllie + +Release Date: August 16, 2005 [EBook #5200] +First posted: May 13, 2002 +Last updated: May 20, 2012 + +Language: English + + +*** START OF THIS PROJECT GUTENBERG EBOOK METAMORPHOSIS *** + + + + +Copyright (C) 2002 David Wyllie. + + + + + + Metamorphosis + Franz Kafka + +Translated by David Wyllie + + + +I + + +One morning, when Gregor Samsa woke from troubled dreams, he found +himself transformed in his bed into a horrible vermin. He lay on +his armour-like back, and if he lifted his head a little he could +see his brown belly, slightly domed and divided by arches into stiff +sections. The bedding was hardly able to cover it and seemed ready +to slide off any moment. His many legs, pitifully thin compared +with the size of the rest of him, waved about helplessly as he +looked. + +"What's happened to me?" he thought. It wasn't a dream. His room, +a proper human room although a little too small, lay peacefully +between its four familiar walls. A collection of textile samples +lay spread out on the table - Samsa was a travelling salesman - and +above it there hung a picture that he had recently cut out of an +illustrated magazine and housed in a nice, gilded frame. It showed +a lady fitted out with a fur hat and fur boa who sat upright, +raising a heavy fur muff that covered the whole of her lower arm +towards the viewer. + +Gregor then turned to look out the window at the dull weather. +Drops of rain could be heard hitting the pane, which made him feel +quite sad. "How about if I sleep a little bit longer and forget all +this nonsense", he thought, but that was something he was unable to +do because he was used to sleeping on his right, and in his present +state couldn't get into that position. However hard he threw +himself onto his right, he always rolled back to where he was. He +must have tried it a hundred times, shut his eyes so that he +wouldn't have to look at the floundering legs, and only stopped when +he began to feel a mild, dull pain there that he had never felt +before. + +"Oh, God", he thought, "what a strenuous career it is that I've +chosen! Travelling day in and day out. Doing business like this +takes much more effort than doing your own business at home, and on +top of that there's the curse of travelling, worries about making +train connections, bad and irregular food, contact with different +people all the time so that you can never get to know anyone or +become friendly with them. It can all go to Hell!" He felt a +slight itch up on his belly; pushed himself slowly up on his back +towards the headboard so that he could lift his head better; found +where the itch was, and saw that it was covered with lots of little +white spots which he didn't know what to make of; and when he tried +to feel the place with one of his legs he drew it quickly back +because as soon as he touched it he was overcome by a cold shudder. + +He slid back into his former position. "Getting up early all the +time", he thought, "it makes you stupid. You've got to get enough +sleep. Other travelling salesmen live a life of luxury. For +instance, whenever I go back to the guest house during the morning +to copy out the contract, these gentlemen are always still sitting +there eating their breakfasts. I ought to just try that with my +boss; I'd get kicked out on the spot. But who knows, maybe that +would be the best thing for me. If I didn't have my parents to +think about I'd have given in my notice a long time ago, I'd have +gone up to the boss and told him just what I think, tell him +everything I would, let him know just what I feel. He'd fall right +off his desk! And it's a funny sort of business to be sitting up +there at your desk, talking down at your subordinates from up there, +especially when you have to go right up close because the boss is +hard of hearing. Well, there's still some hope; once I've got the +money together to pay off my parents' debt to him - another five or +six years I suppose - that's definitely what I'll do. That's when +I'll make the big change. First of all though, I've got to get up, +my train leaves at five." + +And he looked over at the alarm clock, ticking on the chest of +drawers. "God in Heaven!" he thought. It was half past six and the +hands were quietly moving forwards, it was even later than half +past, more like quarter to seven. Had the alarm clock not rung? He +could see from the bed that it had been set for four o'clock as it +should have been; it certainly must have rung. Yes, but was it +possible to quietly sleep through that furniture-rattling noise? +True, he had not slept peacefully, but probably all the more deeply +because of that. What should he do now? The next train went at +seven; if he were to catch that he would have to rush like mad and +the collection of samples was still not packed, and he did not at +all feel particularly fresh and lively. And even if he did catch +the train he would not avoid his boss's anger as the office +assistant would have been there to see the five o'clock train go, he +would have put in his report about Gregor's not being there a long +time ago. The office assistant was the boss's man, spineless, and +with no understanding. What about if he reported sick? But that +would be extremely strained and suspicious as in fifteen years of +service Gregor had never once yet been ill. His boss would +certainly come round with the doctor from the medical insurance +company, accuse his parents of having a lazy son, and accept the +doctor's recommendation not to make any claim as the doctor believed +that no-one was ever ill but that many were workshy. And what's +more, would he have been entirely wrong in this case? Gregor did in +fact, apart from excessive sleepiness after sleeping for so long, +feel completely well and even felt much hungrier than usual. + +He was still hurriedly thinking all this through, unable to decide +to get out of the bed, when the clock struck quarter to seven. +There was a cautious knock at the door near his head. "Gregor", +somebody called - it was his mother - "it's quarter to seven. +Didn't you want to go somewhere?" That gentle voice! Gregor was +shocked when he heard his own voice answering, it could hardly be +recognised as the voice he had had before. As if from deep inside +him, there was a painful and uncontrollable squeaking mixed in with +it, the words could be made out at first but then there was a sort +of echo which made them unclear, leaving the hearer unsure whether +he had heard properly or not. Gregor had wanted to give a full +answer and explain everything, but in the circumstances contented +himself with saying: "Yes, mother, yes, thank-you, I'm getting up +now." The change in Gregor's voice probably could not be noticed +outside through the wooden door, as his mother was satisfied with +this explanation and shuffled away. But this short conversation +made the other members of the family aware that Gregor, against +their expectations was still at home, and soon his father came +knocking at one of the side doors, gently, but with his fist. +"Gregor, Gregor", he called, "what's wrong?" And after a short +while he called again with a warning deepness in his voice: "Gregor! +Gregor!" At the other side door his sister came plaintively: +"Gregor? Aren't you well? Do you need anything?" Gregor answered to +both sides: "I'm ready, now", making an effort to remove all the +strangeness from his voice by enunciating very carefully and putting +long pauses between each, individual word. His father went back to +his breakfast, but his sister whispered: "Gregor, open the door, I +beg of you." Gregor, however, had no thought of opening the door, +and instead congratulated himself for his cautious habit, acquired +from his travelling, of locking all doors at night even when he was +at home. + +The first thing he wanted to do was to get up in peace without being +disturbed, to get dressed, and most of all to have his breakfast. +Only then would he consider what to do next, as he was well aware +that he would not bring his thoughts to any sensible conclusions by +lying in bed. He remembered that he had often felt a slight pain in +bed, perhaps caused by lying awkwardly, but that had always turned +out to be pure imagination and he wondered how his imaginings would +slowly resolve themselves today. He did not have the slightest +doubt that the change in his voice was nothing more than the first +sign of a serious cold, which was an occupational hazard for +travelling salesmen. + +It was a simple matter to throw off the covers; he only had to blow +himself up a little and they fell off by themselves. But it became +difficult after that, especially as he was so exceptionally broad. +He would have used his arms and his hands to push himself up; but +instead of them he only had all those little legs continuously +moving in different directions, and which he was moreover unable to +control. If he wanted to bend one of them, then that was the first +one that would stretch itself out; and if he finally managed to do +what he wanted with that leg, all the others seemed to be set free +and would move about painfully. "This is something that can't be +done in bed", Gregor said to himself, "so don't keep trying to do +it". + +The first thing he wanted to do was get the lower part of his body +out of the bed, but he had never seen this lower part, and could not +imagine what it looked like; it turned out to be too hard to move; +it went so slowly; and finally, almost in a frenzy, when he +carelessly shoved himself forwards with all the force he could +gather, he chose the wrong direction, hit hard against the lower +bedpost, and learned from the burning pain he felt that the lower +part of his body might well, at present, be the most sensitive. + +So then he tried to get the top part of his body out of the bed +first, carefully turning his head to the side. This he managed +quite easily, and despite its breadth and its weight, the bulk of +his body eventually followed slowly in the direction of the head. +But when he had at last got his head out of the bed and into the +fresh air it occurred to him that if he let himself fall it would be +a miracle if his head were not injured, so he became afraid to carry +on pushing himself forward the same way. And he could not knock +himself out now at any price; better to stay in bed than lose +consciousness. + +It took just as much effort to get back to where he had been +earlier, but when he lay there sighing, and was once more watching +his legs as they struggled against each other even harder than +before, if that was possible, he could think of no way of bringing +peace and order to this chaos. He told himself once more that it +was not possible for him to stay in bed and that the most sensible +thing to do would be to get free of it in whatever way he could at +whatever sacrifice. At the same time, though, he did not forget to +remind himself that calm consideration was much better than rushing +to desperate conclusions. At times like this he would direct his +eyes to the window and look out as clearly as he could, but +unfortunately, even the other side of the narrow street was +enveloped in morning fog and the view had little confidence or cheer +to offer him. "Seven o'clock, already", he said to himself when the +clock struck again, "seven o'clock, and there's still a fog like +this." And he lay there quietly a while longer, breathing lightly +as if he perhaps expected the total stillness to bring things back +to their real and natural state. + +But then he said to himself: "Before it strikes quarter past seven +I'll definitely have to have got properly out of bed. And by then +somebody will have come round from work to ask what's happened to me +as well, as they open up at work before seven o'clock." And so he +set himself to the task of swinging the entire length of his body +out of the bed all at the same time. If he succeeded in falling out +of bed in this way and kept his head raised as he did so he could +probably avoid injuring it. His back seemed to be quite hard, and +probably nothing would happen to it falling onto the carpet. His +main concern was for the loud noise he was bound to make, and which +even through all the doors would probably raise concern if not +alarm. But it was something that had to be risked. + +When Gregor was already sticking half way out of the bed - the new +method was more of a game than an effort, all he had to do was rock +back and forth - it occurred to him how simple everything would be +if somebody came to help him. Two strong people - he had his father +and the maid in mind - would have been more than enough; they would +only have to push their arms under the dome of his back, peel him +away from the bed, bend down with the load and then be patient and +careful as he swang over onto the floor, where, hopefully, the +little legs would find a use. Should he really call for help +though, even apart from the fact that all the doors were locked? +Despite all the difficulty he was in, he could not suppress a smile +at this thought. + +After a while he had already moved so far across that it would have +been hard for him to keep his balance if he rocked too hard. The +time was now ten past seven and he would have to make a final +decision very soon. Then there was a ring at the door of the flat. +"That'll be someone from work", he said to himself, and froze very +still, although his little legs only became all the more lively as +they danced around. For a moment everything remained quiet. +"They're not opening the door", Gregor said to himself, caught in +some nonsensical hope. But then of course, the maid's firm steps +went to the door as ever and opened it. Gregor only needed to hear +the visitor's first words of greeting and he knew who it was - the +chief clerk himself. Why did Gregor have to be the only one +condemned to work for a company where they immediately became highly +suspicious at the slightest shortcoming? Were all employees, every +one of them, louts, was there not one of them who was faithful and +devoted who would go so mad with pangs of conscience that he +couldn't get out of bed if he didn't spend at least a couple of +hours in the morning on company business? Was it really not enough +to let one of the trainees make enquiries - assuming enquiries were +even necessary - did the chief clerk have to come himself, and did +they have to show the whole, innocent family that this was so +suspicious that only the chief clerk could be trusted to have the +wisdom to investigate it? And more because these thoughts had made +him upset than through any proper decision, he swang himself with +all his force out of the bed. There was a loud thump, but it wasn't +really a loud noise. His fall was softened a little by the carpet, +and Gregor's back was also more elastic than he had thought, which +made the sound muffled and not too noticeable. He had not held his +head carefully enough, though, and hit it as he fell; annoyed and in +pain, he turned it and rubbed it against the carpet. + +"Something's fallen down in there", said the chief clerk in the room +on the left. Gregor tried to imagine whether something of the sort +that had happened to him today could ever happen to the chief clerk +too; you had to concede that it was possible. But as if in gruff +reply to this question, the chief clerk's firm footsteps in his +highly polished boots could now be heard in the adjoining room. +From the room on his right, Gregor's sister whispered to him to let +him know: "Gregor, the chief clerk is here." "Yes, I know", said +Gregor to himself; but without daring to raise his voice loud enough +for his sister to hear him. + +"Gregor", said his father now from the room to his left, "the chief +clerk has come round and wants to know why you didn't leave on the +early train. We don't know what to say to him. And anyway, he +wants to speak to you personally. So please open up this door. I'm +sure he'll be good enough to forgive the untidiness of your room." +Then the chief clerk called "Good morning, Mr. Samsa". "He isn't +well", said his mother to the chief clerk, while his father +continued to speak through the door. "He isn't well, please believe +me. Why else would Gregor have missed a train! The lad only ever +thinks about the business. It nearly makes me cross the way he +never goes out in the evenings; he's been in town for a week now but +stayed home every evening. He sits with us in the kitchen and just +reads the paper or studies train timetables. His idea of relaxation +is working with his fretsaw. He's made a little frame, for +instance, it only took him two or three evenings, you'll be amazed +how nice it is; it's hanging up in his room; you'll see it as soon +as Gregor opens the door. Anyway, I'm glad you're here; we wouldn't +have been able to get Gregor to open the door by ourselves; he's so +stubborn; and I'm sure he isn't well, he said this morning that he +is, but he isn't." "I'll be there in a moment", said Gregor slowly +and thoughtfully, but without moving so that he would not miss any +word of the conversation. "Well I can't think of any other way of +explaining it, Mrs. Samsa", said the chief clerk, "I hope it's +nothing serious. But on the other hand, I must say that if we +people in commerce ever become slightly unwell then, fortunately or +unfortunately as you like, we simply have to overcome it because of +business considerations." "Can the chief clerk come in to see you +now then?", asked his father impatiently, knocking at the door +again. "No", said Gregor. In the room on his right there followed +a painful silence; in the room on his left his sister began to cry. + +So why did his sister not go and join the others? She had probably +only just got up and had not even begun to get dressed. And why was +she crying? Was it because he had not got up, and had not let the +chief clerk in, because he was in danger of losing his job and if +that happened his boss would once more pursue their parents with the +same demands as before? There was no need to worry about things like +that yet. Gregor was still there and had not the slightest +intention of abandoning his family. For the time being he just lay +there on the carpet, and no-one who knew the condition he was in +would seriously have expected him to let the chief clerk in. It was +only a minor discourtesy, and a suitable excuse could easily be +found for it later on, it was not something for which Gregor could +be sacked on the spot. And it seemed to Gregor much more sensible +to leave him now in peace instead of disturbing him with talking at +him and crying. But the others didn't know what was happening, they +were worried, that would excuse their behaviour. + +The chief clerk now raised his voice, "Mr. Samsa", he called to him, +"what is wrong? You barricade yourself in your room, give us no more +than yes or no for an answer, you are causing serious and +unnecessary concern to your parents and you fail - and I mention +this just by the way - you fail to carry out your business duties in +a way that is quite unheard of. I'm speaking here on behalf of your +parents and of your employer, and really must request a clear and +immediate explanation. I am astonished, quite astonished. I +thought I knew you as a calm and sensible person, and now you +suddenly seem to be showing off with peculiar whims. This morning, +your employer did suggest a possible reason for your failure to +appear, it's true - it had to do with the money that was recently +entrusted to you - but I came near to giving him my word of honour +that that could not be the right explanation. But now that I see +your incomprehensible stubbornness I no longer feel any wish +whatsoever to intercede on your behalf. And nor is your position +all that secure. I had originally intended to say all this to you +in private, but since you cause me to waste my time here for no good +reason I don't see why your parents should not also learn of it. +Your turnover has been very unsatisfactory of late; I grant you that +it's not the time of year to do especially good business, we +recognise that; but there simply is no time of year to do no +business at all, Mr. Samsa, we cannot allow there to be." + +"But Sir", called Gregor, beside himself and forgetting all else in +the excitement, "I'll open up immediately, just a moment. I'm +slightly unwell, an attack of dizziness, I haven't been able to get +up. I'm still in bed now. I'm quite fresh again now, though. I'm +just getting out of bed. Just a moment. Be patient! It's not quite +as easy as I'd thought. I'm quite alright now, though. It's +shocking, what can suddenly happen to a person! I was quite alright +last night, my parents know about it, perhaps better than me, I had +a small symptom of it last night already. They must have noticed +it. I don't know why I didn't let you know at work! But you always +think you can get over an illness without staying at home. Please, +don't make my parents suffer! There's no basis for any of the +accusations you're making; nobody's ever said a word to me about any +of these things. Maybe you haven't read the latest contracts I sent +in. I'll set off with the eight o'clock train, as well, these few +hours of rest have given me strength. You don't need to wait, sir; +I'll be in the office soon after you, and please be so good as to +tell that to the boss and recommend me to him!" + +And while Gregor gushed out these words, hardly knowing what he was +saying, he made his way over to the chest of drawers - this was +easily done, probably because of the practise he had already had in +bed - where he now tried to get himself upright. He really did want +to open the door, really did want to let them see him and to speak +with the chief clerk; the others were being so insistent, and he was +curious to learn what they would say when they caught sight of him. +If they were shocked then it would no longer be Gregor's +responsibility and he could rest. If, however, they took everything +calmly he would still have no reason to be upset, and if he hurried +he really could be at the station for eight o'clock. The first few +times he tried to climb up on the smooth chest of drawers he just +slid down again, but he finally gave himself one last swing and +stood there upright; the lower part of his body was in serious pain +but he no longer gave any attention to it. Now he let himself fall +against the back of a nearby chair and held tightly to the edges of +it with his little legs. By now he had also calmed down, and kept +quiet so that he could listen to what the chief clerk was saying. + +"Did you understand a word of all that?" the chief clerk asked his +parents, "surely he's not trying to make fools of us". "Oh, God!" +called his mother, who was already in tears, "he could be seriously +ill and we're making him suffer. Grete! Grete!" she then cried. +"Mother?" his sister called from the other side. They communicated +across Gregor's room. "You'll have to go for the doctor straight +away. Gregor is ill. Quick, get the doctor. Did you hear the way +Gregor spoke just now?" "That was the voice of an animal", said the +chief clerk, with a calmness that was in contrast with his mother's +screams. "Anna! Anna!" his father called into the kitchen through +the entrance hall, clapping his hands, "get a locksmith here, now!" +And the two girls, their skirts swishing, immediately ran out +through the hall, wrenching open the front door of the flat as they +went. How had his sister managed to get dressed so quickly? There +was no sound of the door banging shut again; they must have left it +open; people often do in homes where something awful has happened. + +Gregor, in contrast, had become much calmer. So they couldn't +understand his words any more, although they seemed clear enough to +him, clearer than before - perhaps his ears had become used to the +sound. They had realised, though, that there was something wrong +with him, and were ready to help. The first response to his +situation had been confident and wise, and that made him feel +better. He felt that he had been drawn back in among people, and +from the doctor and the locksmith he expected great and surprising +achievements - although he did not really distinguish one from the +other. Whatever was said next would be crucial, so, in order to +make his voice as clear as possible, he coughed a little, but taking +care to do this not too loudly as even this might well sound +different from the way that a human coughs and he was no longer sure +he could judge this for himself. Meanwhile, it had become very +quiet in the next room. Perhaps his parents were sat at the table +whispering with the chief clerk, or perhaps they were all pressed +against the door and listening. + +Gregor slowly pushed his way over to the door with the chair. Once +there he let go of it and threw himself onto the door, holding +himself upright against it using the adhesive on the tips of his +legs. He rested there a little while to recover from the effort +involved and then set himself to the task of turning the key in the +lock with his mouth. He seemed, unfortunately, to have no proper +teeth - how was he, then, to grasp the key? - but the lack of teeth +was, of course, made up for with a very strong jaw; using the jaw, +he really was able to start the key turning, ignoring the fact that +he must have been causing some kind of damage as a brown fluid came +from his mouth, flowed over the key and dripped onto the floor. +"Listen", said the chief clerk in the next room, "he's turning the +key." Gregor was greatly encouraged by this; but they all should +have been calling to him, his father and his mother too: "Well done, +Gregor", they should have cried, "keep at it, keep hold of the +lock!" And with the idea that they were all excitedly following his +efforts, he bit on the key with all his strength, paying no +attention to the pain he was causing himself. As the key turned +round he turned around the lock with it, only holding himself +upright with his mouth, and hung onto the key or pushed it down +again with the whole weight of his body as needed. The clear sound +of the lock as it snapped back was Gregor's sign that he could break +his concentration, and as he regained his breath he said to himself: +"So, I didn't need the locksmith after all". Then he lay his head on +the handle of the door to open it completely. + +Because he had to open the door in this way, it was already wide +open before he could be seen. He had first to slowly turn himself +around one of the double doors, and he had to do it very carefully +if he did not want to fall flat on his back before entering the +room. He was still occupied with this difficult movement, unable to +pay attention to anything else, when he heard the chief clerk +exclaim a loud "Oh!", which sounded like the soughing of the wind. +Now he also saw him - he was the nearest to the door - his hand +pressed against his open mouth and slowly retreating as if driven by +a steady and invisible force. Gregor's mother, her hair still +dishevelled from bed despite the chief clerk's being there, looked +at his father. Then she unfolded her arms, took two steps forward +towards Gregor and sank down onto the floor into her skirts that +spread themselves out around her as her head disappeared down onto +her breast. His father looked hostile, and clenched his fists as if +wanting to knock Gregor back into his room. Then he looked +uncertainly round the living room, covered his eyes with his hands +and wept so that his powerful chest shook. + +So Gregor did not go into the room, but leant against the inside of +the other door which was still held bolted in place. In this way +only half of his body could be seen, along with his head above it +which he leant over to one side as he peered out at the others. +Meanwhile the day had become much lighter; part of the endless, +grey-black building on the other side of the street - which was a +hospital - could be seen quite clearly with the austere and regular +line of windows piercing its facade; the rain was still +falling, now throwing down large, individual droplets which hit the +ground one at a time. The washing up from breakfast lay on the +table; there was so much of it because, for Gregor's father, +breakfast was the most important meal of the day and he would +stretch it out for several hours as he sat reading a number of +different newspapers. On the wall exactly opposite there was +photograph of Gregor when he was a lieutenant in the army, his sword +in his hand and a carefree smile on his face as he called forth +respect for his uniform and bearing. The door to the entrance hall +was open and as the front door of the flat was also open he could +see onto the landing and the stairs where they began their way down +below. + +"Now, then", said Gregor, well aware that he was the only one to +have kept calm, "I'll get dressed straight away now, pack up my +samples and set off. Will you please just let me leave? You can +see", he said to the chief clerk, "that I'm not stubborn and I +like to do my job; being a commercial traveller is arduous but +without travelling I couldn't earn my living. So where are you +going, in to the office? Yes? Will you report everything accurately, +then? It's quite possible for someone to be temporarily unable to +work, but that's just the right time to remember what's been +achieved in the past and consider that later on, once the difficulty +has been removed, he will certainly work with all the more diligence +and concentration. You're well aware that I'm seriously in debt to +our employer as well as having to look after my parents and my +sister, so that I'm trapped in a difficult situation, but I will +work my way out of it again. Please don't make things any harder +for me than they are already, and don't take sides against me at the +office. I know that nobody likes the travellers. They think we +earn an enormous wage as well as having a soft time of it. That's +just prejudice but they have no particular reason to think better of +it. But you, sir, you have a better overview than the rest of the +staff, in fact, if I can say this in confidence, a better overview +than the boss himself - it's very easy for a businessman like him to +make mistakes about his employees and judge them more harshly than +he should. And you're also well aware that we travellers spend +almost the whole year away from the office, so that we can very +easily fall victim to gossip and chance and groundless complaints, +and it's almost impossible to defend yourself from that sort of +thing, we don't usually even hear about them, or if at all it's when +we arrive back home exhausted from a trip, and that's when we feel +the harmful effects of what's been going on without even knowing +what caused them. Please, don't go away, at least first say +something to show that you grant that I'm at least partly right!" + +But the chief clerk had turned away as soon as Gregor had started to +speak, and, with protruding lips, only stared back at him over his +trembling shoulders as he left. He did not keep still for a moment +while Gregor was speaking, but moved steadily towards the door +without taking his eyes off him. He moved very gradually, as if +there had been some secret prohibition on leaving the room. It was +only when he had reached the entrance hall that he made a sudden +movement, drew his foot from the living room, and rushed forward in +a panic. In the hall, he stretched his right hand far out towards +the stairway as if out there, there were some supernatural force +waiting to save him. + +Gregor realised that it was out of the question to let the chief +clerk go away in this mood if his position in the firm was not to be +put into extreme danger. That was something his parents did not +understand very well; over the years, they had become convinced that +this job would provide for Gregor for his entire life, and besides, +they had so much to worry about at present that they had lost sight +of any thought for the future. Gregor, though, did think about the +future. The chief clerk had to be held back, calmed down, convinced +and finally won over; the future of Gregor and his family depended +on it! If only his sister were here! She was clever; she was already +in tears while Gregor was still lying peacefully on his back. And +the chief clerk was a lover of women, surely she could persuade him; +she would close the front door in the entrance hall and talk him out +of his shocked state. But his sister was not there, Gregor would +have to do the job himself. And without considering that he still +was not familiar with how well he could move about in his present +state, or that his speech still might not - or probably would not - +be understood, he let go of the door; pushed himself through the +opening; tried to reach the chief clerk on the landing who, +ridiculously, was holding on to the banister with both hands; but +Gregor fell immediately over and, with a little scream as he sought +something to hold onto, landed on his numerous little legs. Hardly +had that happened than, for the first time that day, he began to +feel alright with his body; the little legs had the solid ground +under them; to his pleasure, they did exactly as he told them; they +were even making the effort to carry him where he wanted to go; and +he was soon believing that all his sorrows would soon be finally at +an end. He held back the urge to move but swayed from side to side +as he crouched there on the floor. His mother was not far away in +front of him and seemed, at first, quite engrossed in herself, but +then she suddenly jumped up with her arms outstretched and her +fingers spread shouting: "Help, for pity's sake, Help!" The way she +held her head suggested she wanted to see Gregor better, but the +unthinking way she was hurrying backwards showed that she did not; +she had forgotten that the table was behind her with all the +breakfast things on it; when she reached the table she sat quickly +down on it without knowing what she was doing; without even seeming +to notice that the coffee pot had been knocked over and a gush of +coffee was pouring down onto the carpet. + +"Mother, mother", said Gregor gently, looking up at her. He had +completely forgotten the chief clerk for the moment, but could not +help himself snapping in the air with his jaws at the sight of the +flow of coffee. That set his mother screaming anew, she fled from +the table and into the arms of his father as he rushed towards her. +Gregor, though, had no time to spare for his parents now; the chief +clerk had already reached the stairs; with his chin on the banister, +he looked back for the last time. Gregor made a run for him; he +wanted to be sure of reaching him; the chief clerk must have +expected something, as he leapt down several steps at once and +disappeared; his shouts resounding all around the staircase. The +flight of the chief clerk seemed, unfortunately, to put Gregor's +father into a panic as well. Until then he had been relatively self +controlled, but now, instead of running after the chief clerk +himself, or at least not impeding Gregor as he ran after him, +Gregor's father seized the chief clerk's stick in his right hand +(the chief clerk had left it behind on a chair, along with his hat +and overcoat), picked up a large newspaper from the table with his +left, and used them to drive Gregor back into his room, stamping his +foot at him as he went. Gregor's appeals to his father were of no +help, his appeals were simply not understood, however much he humbly +turned his head his father merely stamped his foot all the harder. +Across the room, despite the chilly weather, Gregor's mother had +pulled open a window, leant far out of it and pressed her hands to +her face. A strong draught of air flew in from the street towards +the stairway, the curtains flew up, the newspapers on the table +fluttered and some of them were blown onto the floor. Nothing would +stop Gregor's father as he drove him back, making hissing noises at +him like a wild man. Gregor had never had any practice in moving +backwards and was only able to go very slowly. If Gregor had only +been allowed to turn round he would have been back in his room +straight away, but he was afraid that if he took the time to do that +his father would become impatient, and there was the threat of a +lethal blow to his back or head from the stick in his father's hand +any moment. Eventually, though, Gregor realised that he had no +choice as he saw, to his disgust, that he was quite incapable of +going backwards in a straight line; so he began, as quickly as +possible and with frequent anxious glances at his father, to turn +himself round. It went very slowly, but perhaps his father was able +to see his good intentions as he did nothing to hinder him, in fact +now and then he used the tip of his stick to give directions from a +distance as to which way to turn. If only his father would stop +that unbearable hissing! It was making Gregor quite confused. When +he had nearly finished turning round, still listening to that +hissing, he made a mistake and turned himself back a little the way +he had just come. He was pleased when he finally had his head in +front of the doorway, but then saw that it was too narrow, and his +body was too broad to get through it without further difficulty. In +his present mood, it obviously did not occur to his father to open +the other of the double doors so that Gregor would have enough space +to get through. He was merely fixed on the idea that Gregor should +be got back into his room as quickly as possible. Nor would he ever +have allowed Gregor the time to get himself upright as preparation +for getting through the doorway. What he did, making more noise +than ever, was to drive Gregor forwards all the harder as if there +had been nothing in the way; it sounded to Gregor as if there was +now more than one father behind him; it was not a pleasant +experience, and Gregor pushed himself into the doorway without +regard for what might happen. One side of his body lifted itself, +he lay at an angle in the doorway, one flank scraped on the white +door and was painfully injured, leaving vile brown flecks on it, +soon he was stuck fast and would not have been able to move at all +by himself, the little legs along one side hung quivering in the air +while those on the other side were pressed painfully against the +ground. Then his father gave him a hefty shove from behind which +released him from where he was held and sent him flying, and heavily +bleeding, deep into his room. The door was slammed shut with the +stick, then, finally, all was quiet. + + + +II + + +It was not until it was getting dark that evening that Gregor awoke +from his deep and coma-like sleep. He would have woken soon +afterwards anyway even if he hadn't been disturbed, as he had had +enough sleep and felt fully rested. But he had the impression that +some hurried steps and the sound of the door leading into the front +room being carefully shut had woken him. The light from the +electric street lamps shone palely here and there onto the ceiling +and tops of the furniture, but down below, where Gregor was, it was +dark. He pushed himself over to the door, feeling his way clumsily +with his antennae - of which he was now beginning to learn the value +- in order to see what had been happening there. The whole of his +left side seemed like one, painfully stretched scar, and he limped +badly on his two rows of legs. One of the legs had been badly +injured in the events of that morning - it was nearly a miracle that +only one of them had been - and dragged along lifelessly. + +It was only when he had reached the door that he realised what it +actually was that had drawn him over to it; it was the smell of +something to eat. By the door there was a dish filled with +sweetened milk with little pieces of white bread floating in it. He +was so pleased he almost laughed, as he was even hungrier than he +had been that morning, and immediately dipped his head into the +milk, nearly covering his eyes with it. But he soon drew his head +back again in disappointment; not only did the pain in his tender +left side make it difficult to eat the food - he was only able to +eat if his whole body worked together as a snuffling whole - but the +milk did not taste at all nice. Milk like this was normally his +favourite drink, and his sister had certainly left it there for him +because of that, but he turned, almost against his own will, away +from the dish and crawled back into the centre of the room. + +Through the crack in the door, Gregor could see that the gas had +been lit in the living room. His father at this time would normally +be sat with his evening paper, reading it out in a loud voice to +Gregor's mother, and sometimes to his sister, but there was now not +a sound to be heard. Gregor's sister would often write and tell him +about this reading, but maybe his father had lost the habit in +recent times. It was so quiet all around too, even though there +must have been somebody in the flat. "What a quiet life it is the +family lead", said Gregor to himself, and, gazing into the darkness, +felt a great pride that he was able to provide a life like that in +such a nice home for his sister and parents. But what now, if all +this peace and wealth and comfort should come to a horrible and +frightening end? That was something that Gregor did not want to +think about too much, so he started to move about, crawling up and +down the room. + +Once during that long evening, the door on one side of the room was +opened very slightly and hurriedly closed again; later on the door +on the other side did the same; it seemed that someone needed to +enter the room but thought better of it. Gregor went and waited +immediately by the door, resolved either to bring the timorous +visitor into the room in some way or at least to find out who it +was; but the door was opened no more that night and Gregor waited in +vain. The previous morning while the doors were locked everyone had +wanted to get in there to him, but now, now that he had opened up +one of the doors and the other had clearly been unlocked some time +during the day, no-one came, and the keys were in the other sides. + +It was not until late at night that the gaslight in the living room +was put out, and now it was easy to see that his parents and sister had +stayed awake all that time, as they all could be distinctly heard as +they went away together on tip-toe. It was clear that no-one would +come into Gregor's room any more until morning; that gave him plenty +of time to think undisturbed about how he would have to re-arrange +his life. For some reason, the tall, empty room where he was forced +to remain made him feel uneasy as he lay there flat on the floor, +even though he had been living in it for five years. Hardly aware +of what he was doing other than a slight feeling of shame, he +hurried under the couch. It pressed down on his back a little, and +he was no longer able to lift his head, but he nonetheless felt +immediately at ease and his only regret was that his body was too +broad to get it all underneath. + +He spent the whole night there. Some of the time he passed in a +light sleep, although he frequently woke from it in alarm because of +his hunger, and some of the time was spent in worries and vague +hopes which, however, always led to the same conclusion: for the +time being he must remain calm, he must show patience and the +greatest consideration so that his family could bear the +unpleasantness that he, in his present condition, was forced to +impose on them. + +Gregor soon had the opportunity to test the strength of his +decisions, as early the next morning, almost before the night had +ended, his sister, nearly fully dressed, opened the door from the +front room and looked anxiously in. She did not see him straight +away, but when she did notice him under the couch - he had to be +somewhere, for God's sake, he couldn't have flown away - she was so +shocked that she lost control of herself and slammed the door shut +again from outside. But she seemed to regret her behaviour, as she +opened the door again straight away and came in on tip-toe as if +entering the room of someone seriously ill or even of a stranger. +Gregor had pushed his head forward, right to the edge of the couch, +and watched her. Would she notice that he had left the milk as it +was, realise that it was not from any lack of hunger and bring him +in some other food that was more suitable? If she didn't do it +herself he would rather go hungry than draw her attention to it, +although he did feel a terrible urge to rush forward from under the +couch, throw himself at his sister's feet and beg her for something +good to eat. However, his sister noticed the full dish immediately +and looked at it and the few drops of milk splashed around it with +some surprise. She immediately picked it up - using a rag, +not her bare hands - and carried it out. Gregor was extremely +curious as to what she would bring in its place, imagining the +wildest possibilities, but he never could have guessed what his +sister, in her goodness, actually did bring. In order to test his +taste, she brought him a whole selection of things, all spread out +on an old newspaper. There were old, half-rotten vegetables; bones +from the evening meal, covered in white sauce that had gone hard; a +few raisins and almonds; some cheese that Gregor had declared +inedible two days before; a dry roll and some bread spread with +butter and salt. As well as all that she had poured some water into +the dish, which had probably been permanently set aside for Gregor's +use, and placed it beside them. Then, out of consideration for +Gregor's feelings, as she knew that he would not eat in front of +her, she hurried out again and even turned the key in the lock so +that Gregor would know he could make things as comfortable for +himself as he liked. Gregor's little legs whirred, at last he could +eat. What's more, his injuries must already have completely healed +as he found no difficulty in moving. This amazed him, as more than +a month earlier he had cut his finger slightly with a knife, he +thought of how his finger had still hurt the day before yesterday. +"Am I less sensitive than I used to be, then?", he thought, and was +already sucking greedily at the cheese which had immediately, almost +compellingly, attracted him much more than the other foods on the +newspaper. Quickly one after another, his eyes watering with +pleasure, he consumed the cheese, the vegetables and the sauce; the +fresh foods, on the other hand, he didn't like at all, and even +dragged the things he did want to eat a little way away from them +because he couldn't stand the smell. Long after he had finished +eating and lay lethargic in the same place, his sister slowly turned +the key in the lock as a sign to him that he should withdraw. He +was immediately startled, although he had been half asleep, and he +hurried back under the couch. But he needed great self-control to +stay there even for the short time that his sister was in the room, +as eating so much food had rounded out his body a little and he +could hardly breathe in that narrow space. Half suffocating, he +watched with bulging eyes as his sister unselfconsciously took a +broom and swept up the left-overs, mixing them in with the food he +had not even touched at all as if it could not be used any more. +She quickly dropped it all into a bin, closed it with its wooden +lid, and carried everything out. She had hardly turned her back +before Gregor came out again from under the couch and stretched +himself. + +This was how Gregor received his food each day now, once in the +morning while his parents and the maid were still asleep, and the +second time after everyone had eaten their meal at midday as his +parents would sleep for a little while then as well, and Gregor's +sister would send the maid away on some errand. Gregor's father and +mother certainly did not want him to starve either, but perhaps it +would have been more than they could stand to have any more +experience of his feeding than being told about it, and perhaps his +sister wanted to spare them what distress she could as they were +indeed suffering enough. + +It was impossible for Gregor to find out what they had told the +doctor and the locksmith that first morning to get them out of the +flat. As nobody could understand him, nobody, not even his sister, +thought that he could understand them, so he had to be content to +hear his sister's sighs and appeals to the saints as she moved about +his room. It was only later, when she had become a little more used +to everything - there was, of course, no question of her ever +becoming fully used to the situation - that Gregor would sometimes +catch a friendly comment, or at least a comment that could be +construed as friendly. "He's enjoyed his dinner today", she might +say when he had diligently cleared away all the food left for him, +or if he left most of it, which slowly became more and more +frequent, she would often say, sadly, "now everything's just been +left there again". + +Although Gregor wasn't able to hear any news directly he did listen +to much of what was said in the next rooms, and whenever he heard +anyone speaking he would scurry straight to the appropriate door and +press his whole body against it. There was seldom any conversation, +especially at first, that was not about him in some way, even if +only in secret. For two whole days, all the talk at every mealtime +was about what they should do now; but even between meals they spoke +about the same subject as there were always at least two members of +the family at home - nobody wanted to be at home by themselves and +it was out of the question to leave the flat entirely empty. And on +the very first day the maid had fallen to her knees and begged +Gregor's mother to let her go without delay. It was not very clear +how much she knew of what had happened but she left within a quarter +of an hour, tearfully thanking Gregor's mother for her dismissal as +if she had done her an enormous service. She even swore +emphatically not to tell anyone the slightest about what had +happened, even though no-one had asked that of her. + +Now Gregor's sister also had to help his mother with the cooking; +although that was not so much bother as no-one ate very much. +Gregor often heard how one of them would unsuccessfully urge another +to eat, and receive no more answer than "no thanks, I've had enough" +or something similar. No-one drank very much either. His sister +would sometimes ask his father whether he would like a beer, hoping +for the chance to go and fetch it herself. When his father then +said nothing she would add, so that he would not feel selfish, that +she could send the housekeeper for it, but then his father would +close the matter with a big, loud "No", and no more would be said. + +Even before the first day had come to an end, his father had +explained to Gregor's mother and sister what their finances and +prospects were. Now and then he stood up from the table and took +some receipt or document from the little cash box he had saved from +his business when it had collapsed five years earlier. Gregor heard +how he opened the complicated lock and then closed it again after he +had taken the item he wanted. What he heard his father say was some +of the first good news that Gregor heard since he had first been +incarcerated in his room. He had thought that nothing at all +remained from his father's business, at least he had never told him +anything different, and Gregor had never asked him about it anyway. +Their business misfortune had reduced the family to a state of total +despair, and Gregor's only concern at that time had been to arrange +things so that they could all forget about it as quickly as +possible. So then he started working especially hard, with a fiery +vigour that raised him from a junior salesman to a travelling +representative almost overnight, bringing with it the chance to earn +money in quite different ways. Gregor converted his success at work +straight into cash that he could lay on the table at home for the +benefit of his astonished and delighted family. They had been good +times and they had never come again, at least not with the same +splendour, even though Gregor had later earned so much that he was +in a position to bear the costs of the whole family, and did bear +them. They had even got used to it, both Gregor and the family, +they took the money with gratitude and he was glad to provide it, +although there was no longer much warm affection given in return. +Gregor only remained close to his sister now. Unlike him, she was +very fond of music and a gifted and expressive violinist, it was his +secret plan to send her to the conservatory next year even though it +would cause great expense that would have to be made up for in some +other way. During Gregor's short periods in town, conversation with +his sister would often turn to the conservatory but it was only ever +mentioned as a lovely dream that could never be realised. Their +parents did not like to hear this innocent talk, but Gregor thought +about it quite hard and decided he would let them know what he +planned with a grand announcement of it on Christmas day. + +That was the sort of totally pointless thing that went through his +mind in his present state, pressed upright against the door and +listening. There were times when he simply became too tired to +continue listening, when his head would fall wearily against the +door and he would pull it up again with a start, as even the +slightest noise he caused would be heard next door and they would +all go silent. "What's that he's doing now", his father would say +after a while, clearly having gone over to the door, and only then +would the interrupted conversation slowly be taken up again. + +When explaining things, his father repeated himself several times, +partly because it was a long time since he had been occupied with +these matters himself and partly because Gregor's mother did not +understand everything the first time. From these repeated explanations +Gregor learned, to his pleasure, that despite all their misfortunes +there was still some money available from the old days. It was not +a lot, but it had not been touched in the meantime and some interest +had accumulated. Besides that, they had not been using up all the +money that Gregor had been bringing home every month, keeping only a +little for himself, so that that, too, had been accumulating. +Behind the door, Gregor nodded with enthusiasm in his pleasure at +this unexpected thrift and caution. He could actually have used +this surplus money to reduce his father's debt to his boss, and the +day when he could have freed himself from that job would have come +much closer, but now it was certainly better the way his father had +done things. + +This money, however, was certainly not enough to enable the family +to live off the interest; it was enough to maintain them for, +perhaps, one or two years, no more. That's to say, it was money +that should not really be touched but set aside for emergencies; +money to live on had to be earned. His father was healthy but old, +and lacking in self confidence. During the five years that he had +not been working - the first holiday in a life that had been full of +strain and no success - he had put on a lot of weight and become +very slow and clumsy. Would Gregor's elderly mother now have to go +and earn money? She suffered from asthma and it was a strain for her +just to move about the home, every other day would be spent +struggling for breath on the sofa by the open window. Would his +sister have to go and earn money? She was still a child of +seventeen, her life up till then had been very enviable, consisting +of wearing nice clothes, sleeping late, helping out in the business, +joining in with a few modest pleasures and most of all playing the +violin. Whenever they began to talk of the need to earn money, +Gregor would always first let go of the door and then throw himself +onto the cool, leather sofa next to it, as he became quite hot with +shame and regret. + +He would often lie there the whole night through, not sleeping a +wink but scratching at the leather for hours on end. Or he might go +to all the effort of pushing a chair to the window, climbing up onto +the sill and, propped up in the chair, leaning on the window to +stare out of it. He had used to feel a great sense of freedom from +doing this, but doing it now was obviously something more remembered +than experienced, as what he actually saw in this way was becoming +less distinct every day, even things that were quite near; he had +used to curse the ever-present view of the hospital across the +street, but now he could not see it at all, and if he had not known +that he lived in Charlottenstrasse, which was a quiet street despite +being in the middle of the city, he could have thought that he was +looking out the window at a barren waste where the grey sky and the +grey earth mingled inseparably. His observant sister only needed to +notice the chair twice before she would always push it back to its +exact position by the window after she had tidied up the room, and +even left the inner pane of the window open from then on. + +If Gregor had only been able to speak to his sister and thank her +for all that she had to do for him it would have been easier for him +to bear it; but as it was it caused him pain. His sister, +naturally, tried as far as possible to pretend there was nothing +burdensome about it, and the longer it went on, of course, the +better she was able to do so, but as time went by Gregor was also +able to see through it all so much better. It had even become very +unpleasant for him, now, whenever she entered the room. No sooner +had she come in than she would quickly close the door as a +precaution so that no-one would have to suffer the view into +Gregor's room, then she would go straight to the window and pull it +hurriedly open almost as if she were suffocating. Even if it was +cold, she would stay at the window breathing deeply for a little +while. She would alarm Gregor twice a day with this running about +and noise making; he would stay under the couch shivering the whole +while, knowing full well that she would certainly have liked to +spare him this ordeal, but it was impossible for her to be in the +same room with him with the windows closed. + +One day, about a month after Gregor's transformation when his sister +no longer had any particular reason to be shocked at his appearance, +she came into the room a little earlier than usual and found him +still staring out the window, motionless, and just where he would be +most horrible. In itself, his sister's not coming into the room +would have been no surprise for Gregor as it would have been +difficult for her to immediately open the window while he was still +there, but not only did she not come in, she went straight back and +closed the door behind her, a stranger would have thought he had +threatened her and tried to bite her. Gregor went straight to hide +himself under the couch, of course, but he had to wait until midday +before his sister came back and she seemed much more uneasy than +usual. It made him realise that she still found his appearance +unbearable and would continue to do so, she probably even had to +overcome the urge to flee when she saw the little bit of him that +protruded from under the couch. One day, in order to spare her even +this sight, he spent four hours carrying the bedsheet over to the +couch on his back and arranged it so that he was completely covered +and his sister would not be able to see him even if she bent down. +If she did not think this sheet was necessary then all she had to do +was take it off again, as it was clear enough that it was no +pleasure for Gregor to cut himself off so completely. She left the +sheet where it was. Gregor even thought he glimpsed a look of +gratitude one time when he carefully looked out from under the sheet +to see how his sister liked the new arrangement. + +For the first fourteen days, Gregor's parents could not bring +themselves to come into the room to see him. He would often hear +them say how they appreciated all the new work his sister was doing +even though, before, they had seen her as a girl who was somewhat +useless and frequently been annoyed with her. But now the two of +them, father and mother, would often both wait outside the door of +Gregor's room while his sister tidied up in there, and as soon as +she went out again she would have to tell them exactly how +everything looked, what Gregor had eaten, how he had behaved this +time and whether, perhaps, any slight improvement could be seen. +His mother also wanted to go in and visit Gregor relatively soon but +his father and sister at first persuaded her against it. Gregor +listened very closely to all this, and approved fully. Later, +though, she had to be held back by force, which made her call out: +"Let me go and see Gregor, he is my unfortunate son! Can't you +understand I have to see him?", and Gregor would think to himself +that maybe it would be better if his mother came in, not every day +of course, but one day a week, perhaps; she could understand +everything much better than his sister who, for all her courage, was +still just a child after all, and really might not have had an +adult's appreciation of the burdensome job she had taken on. + +Gregor's wish to see his mother was soon realised. Out of +consideration for his parents, Gregor wanted to avoid being seen at +the window during the day, the few square meters of the floor did +not give him much room to crawl about, it was hard to just lie +quietly through the night, his food soon stopped giving him any +pleasure at all, and so, to entertain himself, he got into the habit +of crawling up and down the walls and ceiling. He was especially +fond of hanging from the ceiling; it was quite different from lying +on the floor; he could breathe more freely; his body had a light +swing to it; and up there, relaxed and almost happy, it might happen +that he would surprise even himself by letting go of the ceiling and +landing on the floor with a crash. But now, of course, he had far +better control of his body than before and, even with a fall as +great as that, caused himself no damage. Very soon his sister +noticed Gregor's new way of entertaining himself - he had, after +all, left traces of the adhesive from his feet as he crawled about - +and got it into her head to make it as easy as possible for him by +removing the furniture that got in his way, especially the chest of +drawers and the desk. Now, this was not something that she would be +able to do by herself; she did not dare to ask for help from her +father; the sixteen year old maid had carried on bravely since the +cook had left but she certainly would not have helped in this, she +had even asked to be allowed to keep the kitchen locked at all times +and never to have to open the door unless it was especially +important; so his sister had no choice but to choose some time when +Gregor's father was not there and fetch his mother to help her. As +she approached the room, Gregor could hear his mother express her +joy, but once at the door she went silent. First, of course, his +sister came in and looked round to see that everything in the room +was alright; and only then did she let her mother enter. Gregor had +hurriedly pulled the sheet down lower over the couch and put more +folds into it so that everything really looked as if it had just +been thrown down by chance. Gregor also refrained, this time, from +spying out from under the sheet; he gave up the chance to see his +mother until later and was simply glad that she had come. "You can +come in, he can't be seen", said his sister, obviously leading her +in by the hand. The old chest of drawers was too heavy for a pair +of feeble women to be heaving about, but Gregor listened as they +pushed it from its place, his sister always taking on the heaviest +part of the work for herself and ignoring her mother's warnings that +she would strain herself. This lasted a very long time. After +labouring at it for fifteen minutes or more his mother said it would +be better to leave the chest where it was, for one thing it was too +heavy for them to get the job finished before Gregor's father got +home and leaving it in the middle of the room it would be in his way +even more, and for another thing it wasn't even sure that taking the +furniture away would really be any help to him. She thought just +the opposite; the sight of the bare walls saddened her right to her +heart; and why wouldn't Gregor feel the same way about it, he'd been +used to this furniture in his room for a long time and it would make +him feel abandoned to be in an empty room like that. Then, quietly, +almost whispering as if wanting Gregor (whose whereabouts she did +not know) to hear not even the tone of her voice, as she was +convinced that he did not understand her words, she added "and by +taking the furniture away, won't it seem like we're showing that +we've given up all hope of improvement and we're abandoning him to +cope for himself? I think it'd be best to leave the room exactly the +way it was before so that when Gregor comes back to us again he'll +find everything unchanged and he'll be able to forget the time in +between all the easier". + +Hearing these words from his mother made Gregor realise that the +lack of any direct human communication, along with the monotonous +life led by the family during these two months, must have made him +confused - he could think of no other way of explaining to himself +why he had seriously wanted his room emptied out. Had he really +wanted to transform his room into a cave, a warm room fitted out +with the nice furniture he had inherited? That would have let him +crawl around unimpeded in any direction, but it would also have let +him quickly forget his past when he had still been human. He had +come very close to forgetting, and it had only been the voice of his +mother, unheard for so long, that had shaken him out of it. Nothing +should be removed; everything had to stay; he could not do without +the good influence the furniture had on his condition; and if the +furniture made it difficult for him to crawl about mindlessly that +was not a loss but a great advantage. + +His sister, unfortunately, did not agree; she had become used to the +idea, not without reason, that she was Gregor's spokesman to his +parents about the things that concerned him. This meant that his +mother's advice now was sufficient reason for her to insist on +removing not only the chest of drawers and the desk, as she had +thought at first, but all the furniture apart from the all-important +couch. It was more than childish perversity, of course, or the +unexpected confidence she had recently acquired, that made her +insist; she had indeed noticed that Gregor needed a lot of room to +crawl about in, whereas the furniture, as far as anyone could see, +was of no use to him at all. Girls of that age, though, do become +enthusiastic about things and feel they must get their way whenever +they can. Perhaps this was what tempted Grete to make Gregor's +situation seem even more shocking than it was so that she could do +even more for him. Grete would probably be the only one who would +dare enter a room dominated by Gregor crawling about the bare walls +by himself. + +So she refused to let her mother dissuade her. Gregor's mother +already looked uneasy in his room, she soon stopped speaking and +helped Gregor's sister to get the chest of drawers out with what +strength she had. The chest of drawers was something that Gregor +could do without if he had to, but the writing desk had to stay. +Hardly had the two women pushed the chest of drawers, groaning, out +of the room than Gregor poked his head out from under the couch to +see what he could do about it. He meant to be as careful and +considerate as he could, but, unfortunately, it was his mother who +came back first while Grete in the next room had her arms round the +chest, pushing and pulling at it from side to side by herself +without, of course, moving it an inch. His mother was not used to +the sight of Gregor, he might have made her ill, so Gregor hurried +backwards to the far end of the couch. In his startlement, though, +he was not able to prevent the sheet at its front from moving a +little. It was enough to attract his mother's attention. She stood +very still, remained there a moment, and then went back out to +Grete. + +Gregor kept trying to assure himself that nothing unusual was +happening, it was just a few pieces of furniture being moved after +all, but he soon had to admit that the women going to and fro, their +little calls to each other, the scraping of the furniture on the +floor, all these things made him feel as if he were being assailed +from all sides. With his head and legs pulled in against him and +his body pressed to the floor, he was forced to admit to himself +that he could not stand all of this much longer. They were emptying +his room out; taking away everything that was dear to him; they had +already taken out the chest containing his fretsaw and other tools; +now they threatened to remove the writing desk with its place +clearly worn into the floor, the desk where he had done his homework +as a business trainee, at high school, even while he had been at +infant school--he really could not wait any longer to see whether +the two women's intentions were good. He had nearly forgotten they +were there anyway, as they were now too tired to say anything while +they worked and he could only hear their feet as they stepped +heavily on the floor. + +So, while the women were leant against the desk in the other room +catching their breath, he sallied out, changed direction four times +not knowing what he should save first before his attention was +suddenly caught by the picture on the wall - which was already +denuded of everything else that had been on it - of the lady dressed +in copious fur. He hurried up onto the picture and pressed himself +against its glass, it held him firmly and felt good on his hot +belly. This picture at least, now totally covered by Gregor, would +certainly be taken away by no-one. He turned his head to face the +door into the living room so that he could watch the women when they +came back. + +They had not allowed themselves a long rest and came back quite +soon; Grete had put her arm around her mother and was nearly +carrying her. "What shall we take now, then?", said Grete and +looked around. Her eyes met those of Gregor on the wall. Perhaps +only because her mother was there, she remained calm, bent her face +to her so that she would not look round and said, albeit hurriedly +and with a tremor in her voice: "Come on, let's go back in the +living room for a while?" Gregor could see what Grete had in mind, +she wanted to take her mother somewhere safe and then chase him down +from the wall. Well, she could certainly try it! He sat unyielding +on his picture. He would rather jump at Grete's face. + +But Grete's words had made her mother quite worried, she stepped to +one side, saw the enormous brown patch against the flowers of the +wallpaper, and before she even realised it was Gregor that she saw +screamed: "Oh God, oh God!" Arms outstretched, she fell onto the +couch as if she had given up everything and stayed there immobile. +"Gregor!" shouted his sister, glowering at him and shaking her fist. + That was the first word she had spoken to him directly since his +transformation. She ran into the other room to fetch some kind of +smelling salts to bring her mother out of her faint; Gregor wanted +to help too - he could save his picture later, although he stuck +fast to the glass and had to pull himself off by force; then he, +too, ran into the next room as if he could advise his sister like in +the old days; but he had to just stand behind her doing nothing; she +was looking into various bottles, he startled her when she turned +round; a bottle fell to the ground and broke; a splinter cut +Gregor's face, some kind of caustic medicine splashed all over him; +now, without delaying any longer, Grete took hold of all the bottles +she could and ran with them in to her mother; she slammed the door +shut with her foot. So now Gregor was shut out from his mother, +who, because of him, might be near to death; he could not open the +door if he did not want to chase his sister away, and she had to +stay with his mother; there was nothing for him to do but wait; and, +oppressed with anxiety and self-reproach, he began to crawl about, +he crawled over everything, walls, furniture, ceiling, and finally +in his confusion as the whole room began to spin around him he fell +down into the middle of the dinner table. + +He lay there for a while, numb and immobile, all around him it was +quiet, maybe that was a good sign. Then there was someone at the +door. The maid, of course, had locked herself in her kitchen so +that Grete would have to go and answer it. His father had arrived +home. "What's happened?" were his first words; Grete's appearance +must have made everything clear to him. She answered him with +subdued voice, and openly pressed her face into his chest: "Mother's +fainted, but she's better now. Gregor got out." "Just as I +expected", said his father, "just as I always said, but you women +wouldn't listen, would you." It was clear to Gregor that Grete had +not said enough and that his father took it to mean that something +bad had happened, that he was responsible for some act of violence. +That meant Gregor would now have to try to calm his father, as he +did not have the time to explain things to him even if that had been +possible. So he fled to the door of his room and pressed himself +against it so that his father, when he came in from the hall, could +see straight away that Gregor had the best intentions and would go +back into his room without delay, that it would not be necessary to +drive him back but that they had only to open the door and he would +disappear. + +His father, though, was not in the mood to notice subtleties like +that; "Ah!", he shouted as he came in, sounding as if he were both +angry and glad at the same time. Gregor drew his head back from the +door and lifted it towards his father. He really had not imagined +his father the way he stood there now; of late, with his new habit +of crawling about, he had neglected to pay attention to what was +going on the rest of the flat the way he had done before. He really +ought to have expected things to have changed, but still, still, was +that really his father? The same tired man as used to be laying +there entombed in his bed when Gregor came back from his business +trips, who would receive him sitting in the armchair in his +nightgown when he came back in the evenings; who was hardly even +able to stand up but, as a sign of his pleasure, would just raise +his arms and who, on the couple of times a year when they went for a +walk together on a Sunday or public holiday wrapped up tightly in +his overcoat between Gregor and his mother, would always labour his +way forward a little more slowly than them, who were already walking +slowly for his sake; who would place his stick down carefully and, +if he wanted to say something would invariably stop and gather his +companions around him. He was standing up straight enough now; +dressed in a smart blue uniform with gold buttons, the sort worn by +the employees at the banking institute; above the high, stiff collar +of the coat his strong double-chin emerged; under the bushy +eyebrows, his piercing, dark eyes looked out fresh and alert; his +normally unkempt white hair was combed down painfully close to his +scalp. He took his cap, with its gold monogram from, probably, some +bank, and threw it in an arc right across the room onto the sofa, +put his hands in his trouser pockets, pushing back the bottom of his +long uniform coat, and, with look of determination, walked towards +Gregor. He probably did not even know himself what he had in mind, +but nonetheless lifted his feet unusually high. Gregor was amazed +at the enormous size of the soles of his boots, but wasted no time +with that - he knew full well, right from the first day of his new +life, that his father thought it necessary to always be extremely +strict with him. And so he ran up to his father, stopped when his +father stopped, scurried forwards again when he moved, even +slightly. In this way they went round the room several times +without anything decisive happening, without even giving the +impression of a chase as everything went so slowly. Gregor remained +all this time on the floor, largely because he feared his father +might see it as especially provoking if he fled onto the wall or +ceiling. Whatever he did, Gregor had to admit that he certainly +would not be able to keep up this running about for long, as for +each step his father took he had to carry out countless movements. +He became noticeably short of breath, even in his earlier life his +lungs had not been very reliable. Now, as he lurched about in his +efforts to muster all the strength he could for running he could +hardly keep his eyes open; his thoughts became too slow for him to +think of any other way of saving himself than running; he almost +forgot that the walls were there for him to use although, here, they +were concealed behind carefully carved furniture full of notches and +protrusions - then, right beside him, lightly tossed, something flew +down and rolled in front of him. It was an apple; then another one +immediately flew at him; Gregor froze in shock; there was no longer +any point in running as his father had decided to bombard him. He +had filled his pockets with fruit from the bowl on the sideboard and +now, without even taking the time for careful aim, threw one apple +after another. These little, red apples rolled about on the floor, +knocking into each other as if they had electric motors. An apple +thrown without much force glanced against Gregor's back and slid off +without doing any harm. Another one however, immediately following +it, hit squarely and lodged in his back; Gregor wanted to drag +himself away, as if he could remove the surprising, the incredible +pain by changing his position; but he felt as if nailed to the spot +and spread himself out, all his senses in confusion. The last thing +he saw was the door of his room being pulled open, his sister was +screaming, his mother ran out in front of her in her blouse (as his +sister had taken off some of her clothes after she had fainted to +make it easier for her to breathe), she ran to his father, her +skirts unfastened and sliding one after another to the ground, +stumbling over the skirts she pushed herself to his father, her arms +around him, uniting herself with him totally - now Gregor lost his +ability to see anything - her hands behind his father's head begging +him to spare Gregor's life. + + + +III + + +No-one dared to remove the apple lodged in Gregor's flesh, so it +remained there as a visible reminder of his injury. He had suffered +it there for more than a month, and his condition seemed serious +enough to remind even his father that Gregor, despite his current +sad and revolting form, was a family member who could not be treated +as an enemy. On the contrary, as a family there was a duty to +swallow any revulsion for him and to be patient, just to be patient. + +Because of his injuries, Gregor had lost much of his mobility - +probably permanently. He had been reduced to the condition of an +ancient invalid and it took him long, long minutes to crawl across +his room - crawling over the ceiling was out of the question - but +this deterioration in his condition was fully (in his opinion) made +up for by the door to the living room being left open every evening. + He got into the habit of closely watching it for one or two hours +before it was opened and then, lying in the darkness of his room +where he could not be seen from the living room, he could watch the +family in the light of the dinner table and listen to their +conversation - with everyone's permission, in a way, and thus quite +differently from before. + +They no longer held the lively conversations of earlier times, of +course, the ones that Gregor always thought about with longing when +he was tired and getting into the damp bed in some small hotel room. + All of them were usually very quiet nowadays. Soon after dinner, +his father would go to sleep in his chair; his mother and sister +would urge each other to be quiet; his mother, bent deeply under the +lamp, would sew fancy underwear for a fashion shop; his sister, who +had taken a sales job, learned shorthand and French in the evenings +so that she might be able to get a better position later on. +Sometimes his father would wake up and say to Gregor's mother +"you're doing so much sewing again today!", as if he did not know +that he had been dozing - and then he would go back to sleep again +while mother and sister would exchange a tired grin. + +With a kind of stubbornness, Gregor's father refused to take his +uniform off even at home; while his nightgown hung unused on its peg +Gregor's father would slumber where he was, fully dressed, as if +always ready to serve and expecting to hear the voice of his +superior even here. The uniform had not been new to start with, but +as a result of this it slowly became even shabbier despite the +efforts of Gregor's mother and sister to look after it. Gregor +would often spend the whole evening looking at all the stains on +this coat, with its gold buttons always kept polished and shiny, +while the old man in it would sleep, highly uncomfortable but +peaceful. + +As soon as it struck ten, Gregor's mother would speak gently to his +father to wake him and try to persuade him to go to bed, as he +couldn't sleep properly where he was and he really had to get his +sleep if he was to be up at six to get to work. But since he had +been in work he had become more obstinate and would always insist on +staying longer at the table, even though he regularly fell asleep +and it was then harder than ever to persuade him to exchange the +chair for his bed. Then, however much mother and sister would +importune him with little reproaches and warnings he would keep +slowly shaking his head for a quarter of an hour with his eyes +closed and refusing to get up. Gregor's mother would tug at his +sleeve, whisper endearments into his ear, Gregor's sister would +leave her work to help her mother, but nothing would have any effect +on him. He would just sink deeper into his chair. Only when the +two women took him under the arms he would abruptly open his eyes, +look at them one after the other and say: "What a life! This is what +peace I get in my old age!" And supported by the two women he would +lift himself up carefully as if he were carrying the greatest load +himself, let the women take him to the door, send them off and carry +on by himself while Gregor's mother would throw down her needle and +his sister her pen so that they could run after his father and +continue being of help to him. + +Who, in this tired and overworked family, would have had time to +give more attention to Gregor than was absolutely necessary? The +household budget became even smaller; so now the maid was dismissed; +an enormous, thick-boned charwoman with white hair that flapped +around her head came every morning and evening to do the heaviest +work; everything else was looked after by Gregor's mother on top of +the large amount of sewing work she did. Gregor even learned, +listening to the evening conversation about what price they had +hoped for, that several items of jewellery belonging to the family +had been sold, even though both mother and sister had been very fond +of wearing them at functions and celebrations. But the loudest +complaint was that although the flat was much too big for their +present circumstances, they could not move out of it, there was no +imaginable way of transferring Gregor to the new address. He could +see quite well, though, that there were more reasons than +consideration for him that made it difficult for them to move, it +would have been quite easy to transport him in any suitable crate +with a few air holes in it; the main thing holding the family back +from their decision to move was much more to do with their total +despair, and the thought that they had been struck with a misfortune +unlike anything experienced by anyone else they knew or were related +to. They carried out absolutely everything that the world expects +from poor people, Gregor's father brought bank employees their +breakfast, his mother sacrificed herself by washing clothes for +strangers, his sister ran back and forth behind her desk at the +behest of the customers, but they just did not have the strength to +do any more. And the injury in Gregor's back began to hurt as much +as when it was new. After they had come back from taking his father +to bed Gregor's mother and sister would now leave their work where +it was and sit close together, cheek to cheek; his mother would +point to Gregor's room and say "Close that door, Grete", and then, +when he was in the dark again, they would sit in the next room and +their tears would mingle, or they would simply sit there staring +dry-eyed at the table. + +Gregor hardly slept at all, either night or day. Sometimes he would +think of taking over the family's affairs, just like before, the +next time the door was opened; he had long forgotten about his boss +and the chief clerk, but they would appear again in his thoughts, +the salesmen and the apprentices, that stupid teaboy, two or three +friends from other businesses, one of the chambermaids from a +provincial hotel, a tender memory that appeared and disappeared +again, a cashier from a hat shop for whom his attention had been +serious but too slow, - all of them appeared to him, mixed together +with strangers and others he had forgotten, but instead of helping +him and his family they were all of them inaccessible, and he was +glad when they disappeared. Other times he was not at all in the +mood to look after his family, he was filled with simple rage about +the lack of attention he was shown, and although he could think of +nothing he would have wanted, he made plans of how he could get into +the pantry where he could take all the things he was entitled to, +even if he was not hungry. Gregor's sister no longer thought about +how she could please him but would hurriedly push some food or other +into his room with her foot before she rushed out to work in the +morning and at midday, and in the evening she would sweep it away +again with the broom, indifferent as to whether it had been eaten or +- more often than not - had been left totally untouched. She still +cleared up the room in the evening, but now she could not have been +any quicker about it. Smears of dirt were left on the walls, here +and there were little balls of dust and filth. At first, Gregor +went into one of the worst of these places when his sister arrived +as a reproach to her, but he could have stayed there for weeks +without his sister doing anything about it; she could see the dirt +as well as he could but she had simply decided to leave him to it. +At the same time she became touchy in a way that was quite new for +her and which everyone in the family understood - cleaning up +Gregor's room was for her and her alone. Gregor's mother did once +thoroughly clean his room, and needed to use several bucketfuls of +water to do it - although that much dampness also made Gregor ill +and he lay flat on the couch, bitter and immobile. But his mother +was to be punished still more for what she had done, as hardly had +his sister arrived home in the evening than she noticed the change +in Gregor's room and, highly aggrieved, ran back into the living +room where, despite her mothers raised and imploring hands, she +broke into convulsive tears. Her father, of course, was startled +out of his chair and the two parents looked on astonished and +helpless; then they, too, became agitated; Gregor's father, standing +to the right of his mother, accused her of not leaving the cleaning +of Gregor's room to his sister; from her left, Gregor's sister +screamed at her that she was never to clean Gregor's room again; +while his mother tried to draw his father, who was beside himself +with anger, into the bedroom; his sister, quaking with tears, +thumped on the table with her small fists; and Gregor hissed in +anger that no-one had even thought of closing the door to save him +the sight of this and all its noise. + +Gregor's sister was exhausted from going out to work, and looking +after Gregor as she had done before was even more work for her, but +even so his mother ought certainly not to have taken her place. +Gregor, on the other hand, ought not to be neglected. Now, though, +the charwoman was here. This elderly widow, with a robust bone +structure that made her able to withstand the hardest of things in +her long life, wasn't really repelled by Gregor. Just by chance one +day, rather than any real curiosity, she opened the door to Gregor's +room and found herself face to face with him. He was taken totally +by surprise, no-one was chasing him but he began to rush to and fro +while she just stood there in amazement with her hands crossed in +front of her. From then on she never failed to open the door +slightly every evening and morning and look briefly in on him. At +first she would call to him as she did so with words that she +probably considered friendly, such as "come on then, you old +dung-beetle!", or "look at the old dung-beetle there!" Gregor never +responded to being spoken to in that way, but just remained where he +was without moving as if the door had never even been opened. If +only they had told this charwoman to clean up his room every day +instead of letting her disturb him for no reason whenever she felt +like it! One day, early in the morning while a heavy rain struck the +windowpanes, perhaps indicating that spring was coming, she began to +speak to him in that way once again. Gregor was so resentful of it +that he started to move toward her, he was slow and infirm, but it +was like a kind of attack. Instead of being afraid, the charwoman +just lifted up one of the chairs from near the door and stood there +with her mouth open, clearly intending not to close her mouth until +the chair in her hand had been slammed down into Gregor's back. +"Aren't you coming any closer, then?", she asked when Gregor turned +round again, and she calmly put the chair back in the corner. + +Gregor had almost entirely stopped eating. Only if he happened to +find himself next to the food that had been prepared for him he +might take some of it into his mouth to play with it, leave it there +a few hours and then, more often than not, spit it out again. At +first he thought it was distress at the state of his room that +stopped him eating, but he had soon got used to the changes made +there. They had got into the habit of putting things into this room +that they had no room for anywhere else, and there were now many +such things as one of the rooms in the flat had been rented out to +three gentlemen. These earnest gentlemen - all three of them had +full beards, as Gregor learned peering through the crack in the door +one day - were painfully insistent on things' being tidy. This +meant not only in their own room but, since they had taken a room in +this establishment, in the entire flat and especially in the +kitchen. Unnecessary clutter was something they could not tolerate, +especially if it was dirty. They had moreover brought most of their +own furnishings and equipment with them. For this reason, many +things had become superfluous which, although they could not be +sold, the family did not wish to discard. All these things found +their way into Gregor's room. The dustbins from the kitchen found +their way in there too. The charwoman was always in a hurry, and +anything she couldn't use for the time being she would just chuck in +there. He, fortunately, would usually see no more than the object +and the hand that held it. The woman most likely meant to fetch the +things back out again when she had time and the opportunity, or to +throw everything out in one go, but what actually happened was that +they were left where they landed when they had first been thrown +unless Gregor made his way through the junk and moved it somewhere +else. At first he moved it because, with no other room free where +he could crawl about, he was forced to, but later on he came to +enjoy it although moving about in that way left him sad and tired to +death, and he would remain immobile for hours afterwards. + +The gentlemen who rented the room would sometimes take their evening +meal at home in the living room that was used by everyone, and so +the door to this room was often kept closed in the evening. But +Gregor found it easy to give up having the door open, he had, after +all, often failed to make use of it when it was open and, without +the family having noticed it, lain in his room in its darkest +corner. One time, though, the charwoman left the door to the living +room slightly open, and it remained open when the gentlemen who +rented the room came in in the evening and the light was put on. +They sat up at the table where, formerly, Gregor had taken his meals +with his father and mother, they unfolded the serviettes and picked +up their knives and forks. Gregor's mother immediately appeared in +the doorway with a dish of meat and soon behind her came his sister +with a dish piled high with potatoes. The food was steaming, and +filled the room with its smell. The gentlemen bent over the dishes +set in front of them as if they wanted to test the food before +eating it, and the gentleman in the middle, who seemed to count as +an authority for the other two, did indeed cut off a piece of meat +while it was still in its dish, clearly wishing to establish whether +it was sufficiently cooked or whether it should be sent back to the +kitchen. It was to his satisfaction, and Gregor's mother and +sister, who had been looking on anxiously, began to breathe again +and smiled. + +The family themselves ate in the kitchen. Nonetheless, Gregor's +father came into the living room before he went into the kitchen, +bowed once with his cap in his hand and did his round of the table. +The gentlemen stood as one, and mumbled something into their beards. + Then, once they were alone, they ate in near perfect silence. It +seemed remarkable to Gregor that above all the various noises of +eating their chewing teeth could still be heard, as if they had +wanted to show Gregor that you need teeth in order to eat and it was +not possible to perform anything with jaws that are toothless +however nice they might be. "I'd like to eat something", said +Gregor anxiously, "but not anything like they're eating. They do +feed themselves. And here I am, dying!" + +Throughout all this time, Gregor could not remember having heard the +violin being played, but this evening it began to be heard from the +kitchen. The three gentlemen had already finished their meal, the +one in the middle had produced a newspaper, given a page to each of +the others, and now they leant back in their chairs reading them and +smoking. When the violin began playing they became attentive, stood +up and went on tip-toe over to the door of the hallway where they +stood pressed against each other. Someone must have heard them in +the kitchen, as Gregor's father called out: "Is the playing perhaps +unpleasant for the gentlemen? We can stop it straight away." "On +the contrary", said the middle gentleman, "would the young lady not +like to come in and play for us here in the room, where it is, after +all, much more cosy and comfortable?" "Oh yes, we'd love to", +called back Gregor's father as if he had been the violin player +himself. The gentlemen stepped back into the room and waited. +Gregor's father soon appeared with the music stand, his mother with +the music and his sister with the violin. She calmly prepared +everything for her to begin playing; his parents, who had never +rented a room out before and therefore showed an exaggerated +courtesy towards the three gentlemen, did not even dare to sit on +their own chairs; his father leant against the door with his right +hand pushed in between two buttons on his uniform coat; his mother, +though, was offered a seat by one of the gentlemen and sat - leaving +the chair where the gentleman happened to have placed it - out of +the way in a corner. + +His sister began to play; father and mother paid close attention, +one on each side, to the movements of her hands. Drawn in by the +playing, Gregor had dared to come forward a little and already had +his head in the living room. Before, he had taken great pride in +how considerate he was but now it hardly occurred to him that he had +become so thoughtless about the others. What's more, there was now +all the more reason to keep himself hidden as he was covered in the +dust that lay everywhere in his room and flew up at the slightest +movement; he carried threads, hairs, and remains of food about on +his back and sides; he was much too indifferent to everything now to +lay on his back and wipe himself on the carpet like he had used to +do several times a day. And despite this condition, he was not too +shy to move forward a little onto the immaculate floor of the living +room. + +No-one noticed him, though. The family was totally preoccupied with +the violin playing; at first, the three gentlemen had put their +hands in their pockets and come up far too close behind the music +stand to look at all the notes being played, and they must have +disturbed Gregor's sister, but soon, in contrast with the family, +they withdrew back to the window with their heads sunk and talking +to each other at half volume, and they stayed by the window while +Gregor's father observed them anxiously. It really now seemed very +obvious that they had expected to hear some beautiful or +entertaining violin playing but had been disappointed, that they had +had enough of the whole performance and it was only now out of +politeness that they allowed their peace to be disturbed. It was +especially unnerving, the way they all blew the smoke from their +cigarettes upwards from their mouth and noses. Yet Gregor's sister +was playing so beautifully. Her face was leant to one side, +following the lines of music with a careful and melancholy +expression. Gregor crawled a little further forward, keeping his +head close to the ground so that he could meet her eyes if the +chance came. Was he an animal if music could captivate him so? It +seemed to him that he was being shown the way to the unknown +nourishment he had been yearning for. He was determined to make his +way forward to his sister and tug at her skirt to show her she might +come into his room with her violin, as no-one appreciated her +playing here as much as he would. He never wanted to let her out of +his room, not while he lived, anyway; his shocking appearance +should, for once, be of some use to him; he wanted to be at every +door of his room at once to hiss and spit at the attackers; his +sister should not be forced to stay with him, though, but stay of +her own free will; she would sit beside him on the couch with her +ear bent down to him while he told her how he had always intended to +send her to the conservatory, how he would have told everyone about +it last Christmas - had Christmas really come and gone already? - if +this misfortune hadn't got in the way, and refuse to let anyone +dissuade him from it. On hearing all this, his sister would break +out in tears of emotion, and Gregor would climb up to her shoulder +and kiss her neck, which, since she had been going out to work, she +had kept free without any necklace or collar. + +"Mr. Samsa!", shouted the middle gentleman to Gregor's father, +pointing, without wasting any more words, with his forefinger at +Gregor as he slowly moved forward. The violin went silent, the +middle of the three gentlemen first smiled at his two friends, +shaking his head, and then looked back at Gregor. His father seemed +to think it more important to calm the three gentlemen before +driving Gregor out, even though they were not at all upset and +seemed to think Gregor was more entertaining than the violin playing +had been. He rushed up to them with his arms spread out and +attempted to drive them back into their room at the same time as +trying to block their view of Gregor with his body. Now they did +become a little annoyed, and it was not clear whether it was his +father's behaviour that annoyed them or the dawning realisation that +they had had a neighbour like Gregor in the next room without +knowing it. They asked Gregor's father for explanations, raised +their arms like he had, tugged excitedly at their beards and moved +back towards their room only very slowly. Meanwhile Gregor's sister +had overcome the despair she had fallen into when her playing was +suddenly interrupted. She had let her hands drop and let violin and +bow hang limply for a while but continued to look at the music as if +still playing, but then she suddenly pulled herself together, lay +the instrument on her mother's lap who still sat laboriously +struggling for breath where she was, and ran into the next room +which, under pressure from her father, the three gentlemen were more +quickly moving toward. Under his sister's experienced hand, the +pillows and covers on the beds flew up and were put into order and +she had already finished making the beds and slipped out again +before the three gentlemen had reached the room. Gregor's father +seemed so obsessed with what he was doing that he forgot all the +respect he owed to his tenants. He urged them and pressed them +until, when he was already at the door of the room, the middle of +the three gentlemen shouted like thunder and stamped his foot and +thereby brought Gregor's father to a halt. "I declare here and +now", he said, raising his hand and glancing at Gregor's mother and +sister to gain their attention too, "that with regard to the +repugnant conditions that prevail in this flat and with this family" +- here he looked briefly but decisively at the floor - "I give +immediate notice on my room. For the days that I have been living +here I will, of course, pay nothing at all, on the contrary I will +consider whether to proceed with some kind of action for damages +from you, and believe me it would be very easy to set out the +grounds for such an action." He was silent and looked straight +ahead as if waiting for something. And indeed, his two friends +joined in with the words: "And we also give immediate notice." With +that, he took hold of the door handle and slammed the door. + +Gregor's father staggered back to his seat, feeling his way with his +hands, and fell into it; it looked as if he was stretching himself +out for his usual evening nap but from the uncontrolled way his head +kept nodding it could be seen that he was not sleeping at all. +Throughout all this, Gregor had lain still where the three gentlemen +had first seen him. His disappointment at the failure of his plan, +and perhaps also because he was weak from hunger, made it impossible +for him to move. He was sure that everyone would turn on him any +moment, and he waited. He was not even startled out of this state +when the violin on his mother's lap fell from her trembling fingers +and landed loudly on the floor. + +"Father, Mother", said his sister, hitting the table with her hand +as introduction, "we can't carry on like this. Maybe you can't see +it, but I can. I don't want to call this monster my brother, all I +can say is: we have to try and get rid of it. We've done all that's +humanly possible to look after it and be patient, I don't think +anyone could accuse us of doing anything wrong." + +"She's absolutely right", said Gregor's father to himself. His +mother, who still had not had time to catch her breath, began to +cough dully, her hand held out in front of her and a deranged +expression in her eyes. + +Gregor's sister rushed to his mother and put her hand on her +forehead. Her words seemed to give Gregor's father some more +definite ideas. He sat upright, played with his uniform cap between +the plates left by the three gentlemen after their meal, and +occasionally looked down at Gregor as he lay there immobile. + +"We have to try and get rid of it", said Gregor's sister, now +speaking only to her father, as her mother was too occupied with +coughing to listen, "it'll be the death of both of you, I can see it +coming. We can't all work as hard as we have to and then come home +to be tortured like this, we can't endure it. I can't endure it any +more." And she broke out so heavily in tears that they flowed down +the face of her mother, and she wiped them away with mechanical hand +movements. + +"My child", said her father with sympathy and obvious understanding, +"what are we to do?" + +His sister just shrugged her shoulders as a sign of the helplessness +and tears that had taken hold of her, displacing her earlier +certainty. + +"If he could just understand us", said his father almost as a +question; his sister shook her hand vigorously through her tears as +a sign that of that there was no question. + +"If he could just understand us", repeated Gregor's father, closing +his eyes in acceptance of his sister's certainty that that was quite +impossible, "then perhaps we could come to some kind of arrangement +with him. But as it is ..." + +"It's got to go", shouted his sister, "that's the only way, Father. +You've got to get rid of the idea that that's Gregor. We've only +harmed ourselves by believing it for so long. How can that be +Gregor? If it were Gregor he would have seen long ago that it's not +possible for human beings to live with an animal like that and he +would have gone of his own free will. We wouldn't have a brother +any more, then, but we could carry on with our lives and remember +him with respect. As it is this animal is persecuting us, it's +driven out our tenants, it obviously wants to take over the whole +flat and force us to sleep on the streets. Father, look, just +look", she suddenly screamed, "he's starting again!" In her alarm, +which was totally beyond Gregor's comprehension, his sister even +abandoned his mother as she pushed herself vigorously out of her +chair as if more willing to sacrifice her own mother than stay +anywhere near Gregor. She rushed over to behind her father, who had +become excited merely because she was and stood up half raising his +hands in front of Gregor's sister as if to protect her. + +But Gregor had had no intention of frightening anyone, least of all +his sister. All he had done was begin to turn round so that he +could go back into his room, although that was in itself quite +startling as his pain-wracked condition meant that turning round +required a great deal of effort and he was using his head to help +himself do it, repeatedly raising it and striking it against the +floor. He stopped and looked round. They seemed to have realised +his good intention and had only been alarmed briefly. Now they all +looked at him in unhappy silence. His mother lay in her chair with +her legs stretched out and pressed against each other, her eyes +nearly closed with exhaustion; his sister sat next to his father +with her arms around his neck. + +"Maybe now they'll let me turn round", thought Gregor and went back +to work. He could not help panting loudly with the effort and had +sometimes to stop and take a rest. No-one was making him rush any +more, everything was left up to him. As soon as he had finally +finished turning round he began to move straight ahead. He was +amazed at the great distance that separated him from his room, and +could not understand how he had covered that distance in his weak +state a little while before and almost without noticing it. He +concentrated on crawling as fast as he could and hardly noticed that +there was not a word, not any cry, from his family to distract him. +He did not turn his head until he had reached the doorway. He did +not turn it all the way round as he felt his neck becoming stiff, +but it was nonetheless enough to see that nothing behind him had +changed, only his sister had stood up. With his last glance he saw +that his mother had now fallen completely asleep. + +He was hardly inside his room before the door was hurriedly shut, +bolted and locked. The sudden noise behind Gregor so startled him +that his little legs collapsed under him. It was his sister who had +been in so much of a rush. She had been standing there waiting and +sprung forward lightly, Gregor had not heard her coming at all, and +as she turned the key in the lock she said loudly to her parents "At +last!". + +"What now, then?", Gregor asked himself as he looked round in the +darkness. He soon made the discovery that he could no longer move +at all. This was no surprise to him, it seemed rather that being +able to actually move around on those spindly little legs until then +was unnatural. He also felt relatively comfortable. It is true +that his entire body was aching, but the pain seemed to be slowly +getting weaker and weaker and would finally disappear altogether. +He could already hardly feel the decayed apple in his back or the +inflamed area around it, which was entirely covered in white dust. +He thought back of his family with emotion and love. If it was +possible, he felt that he must go away even more strongly than his +sister. He remained in this state of empty and peaceful rumination +until he heard the clock tower strike three in the morning. He +watched as it slowly began to get light everywhere outside the +window too. Then, without his willing it, his head sank down +completely, and his last breath flowed weakly from his nostrils. + +When the cleaner came in early in the morning - they'd often asked +her not to keep slamming the doors but with her strength and in her +hurry she still did, so that everyone in the flat knew when she'd +arrived and from then on it was impossible to sleep in peace - she +made her usual brief look in on Gregor and at first found nothing +special. She thought he was laying there so still on purpose, +playing the martyr; she attributed all possible understanding to +him. She happened to be holding the long broom in her hand, so she +tried to tickle Gregor with it from the doorway. When she had no +success with that she tried to make a nuisance of herself and poked +at him a little, and only when she found she could shove him across +the floor with no resistance at all did she start to pay attention. +She soon realised what had really happened, opened her eyes wide, +whistled to herself, but did not waste time to yank open the bedroom +doors and shout loudly into the darkness of the bedrooms: "Come and +'ave a look at this, it's dead, just lying there, stone dead!" + +Mr. and Mrs. Samsa sat upright there in their marriage bed and had +to make an effort to get over the shock caused by the cleaner before +they could grasp what she was saying. But then, each from his own +side, they hurried out of bed. Mr. Samsa threw the blanket over his +shoulders, Mrs. Samsa just came out in her nightdress; and that is +how they went into Gregor's room. On the way they opened the door +to the living room where Grete had been sleeping since the three +gentlemen had moved in; she was fully dressed as if she had never +been asleep, and the paleness of her face seemed to confirm this. +"Dead?", asked Mrs. Samsa, looking at the charwoman enquiringly, +even though she could have checked for herself and could have known +it even without checking. "That's what I said", replied the +cleaner, and to prove it she gave Gregor's body another shove with +the broom, sending it sideways across the floor. Mrs. Samsa made a +movement as if she wanted to hold back the broom, but did not +complete it. "Now then", said Mr. Samsa, "let's give thanks to God +for that". He crossed himself, and the three women followed his +example. Grete, who had not taken her eyes from the corpse, said: +"Just look how thin he was. He didn't eat anything for so long. +The food came out again just the same as when it went in". Gregor's +body was indeed completely dried up and flat, they had not seen it +until then, but now he was not lifted up on his little legs, nor did +he do anything to make them look away. + +"Grete, come with us in here for a little while", said Mrs. Samsa +with a pained smile, and Grete followed her parents into the bedroom +but not without looking back at the body. The cleaner shut the door +and opened the window wide. Although it was still early in the +morning the fresh air had something of warmth mixed in with it. It +was already the end of March, after all. + +The three gentlemen stepped out of their room and looked round in +amazement for their breakfasts; they had been forgotten about. +"Where is our breakfast?", the middle gentleman asked the cleaner +irritably. She just put her finger on her lips and made a quick and +silent sign to the men that they might like to come into Gregor's +room. They did so, and stood around Gregor's corpse with their +hands in the pockets of their well-worn coats. It was now quite +light in the room. + +Then the door of the bedroom opened and Mr. Samsa appeared in his +uniform with his wife on one arm and his daughter on the other. All +of them had been crying a little; Grete now and then pressed her +face against her father's arm. + +"Leave my home. Now!", said Mr. Samsa, indicating the door and +without letting the women from him. "What do you mean?", asked the +middle of the three gentlemen somewhat disconcerted, and he smiled +sweetly. The other two held their hands behind their backs and +continually rubbed them together in gleeful anticipation of a loud +quarrel which could only end in their favour. "I mean just what I +said", answered Mr. Samsa, and, with his two companions, went in a +straight line towards the man. At first, he stood there still, +looking at the ground as if the contents of his head were +rearranging themselves into new positions. "Alright, we'll go +then", he said, and looked up at Mr. Samsa as if he had been +suddenly overcome with humility and wanted permission again from +Mr. Samsa for his decision. Mr. Samsa merely opened his eyes wide +and briefly nodded to him several times. At that, and without +delay, the man actually did take long strides into the front +hallway; his two friends had stopped rubbing their hands some time +before and had been listening to what was being said. Now they +jumped off after their friend as if taken with a sudden fear that +Mr. Samsa might go into the hallway in front of them and break the +connection with their leader. Once there, all three took their hats +from the stand, took their sticks from the holder, bowed without a +word and left the premises. Mr. Samsa and the two women followed +them out onto the landing; but they had had no reason to mistrust +the men's intentions and as they leaned over the landing they saw how +the three gentlemen made slow but steady progress down the many +steps. As they turned the corner on each floor they disappeared and +would reappear a few moments later; the further down they went, the +more that the Samsa family lost interest in them; when a butcher's +boy, proud of posture with his tray on his head, passed them on his +way up and came nearer than they were, Mr. Samsa and the women came +away from the landing and went, as if relieved, back into the flat. + +They decided the best way to make use of that day was for relaxation +and to go for a walk; not only had they earned a break from work but +they were in serious need of it. So they sat at the table and wrote +three letters of excusal, Mr. Samsa to his employers, Mrs. Samsa +to her contractor and Grete to her principal. The cleaner came in +while they were writing to tell them she was going, she'd finished +her work for that morning. The three of them at first just nodded +without looking up from what they were writing, and it was only when +the cleaner still did not seem to want to leave that they looked up +in irritation. "Well?", asked Mr. Samsa. The charwoman stood in +the doorway with a smile on her face as if she had some tremendous +good news to report, but would only do it if she was clearly asked +to. The almost vertical little ostrich feather on her hat, which +had been a source of irritation to Mr. Samsa all the time she had +been working for them, swayed gently in all directions. "What is it +you want then?", asked Mrs. Samsa, whom the cleaner had the most +respect for. "Yes", she answered, and broke into a friendly laugh +that made her unable to speak straight away, "well then, that thing +in there, you needn't worry about how you're going to get rid of it. + That's all been sorted out." Mrs. Samsa and Grete bent down over +their letters as if intent on continuing with what they were +writing; Mr. Samsa saw that the cleaner wanted to start describing +everything in detail but, with outstretched hand, he made it quite +clear that she was not to. So, as she was prevented from telling +them all about it, she suddenly remembered what a hurry she was in +and, clearly peeved, called out "Cheerio then, everyone", turned +round sharply and left, slamming the door terribly as she went. + +"Tonight she gets sacked", said Mr. Samsa, but he received no reply +from either his wife or his daughter as the charwoman seemed to have +destroyed the peace they had only just gained. They got up and went +over to the window where they remained with their arms around each +other. Mr. Samsa twisted round in his chair to look at them and sat +there watching for a while. Then he called out: "Come here, then. +Let's forget about all that old stuff, shall we. Come and give me a +bit of attention". The two women immediately did as he said, +hurrying over to him where they kissed him and hugged him and then +they quickly finished their letters. + +After that, the three of them left the flat together, which was +something they had not done for months, and took the tram out to the +open country outside the town. They had the tram, filled with warm +sunshine, all to themselves. Leant back comfortably on their seats, +they discussed their prospects and found that on closer examination +they were not at all bad - until then they had never asked each +other about their work but all three had jobs which were very good +and held particularly good promise for the future. The greatest +improvement for the time being, of course, would be achieved quite +easily by moving house; what they needed now was a flat that was +smaller and cheaper than the current one which had been chosen by +Gregor, one that was in a better location and, most of all, more +practical. All the time, Grete was becoming livelier. With all the +worry they had been having of late her cheeks had become pale, but, +while they were talking, Mr. and Mrs. Samsa were struck, almost +simultaneously, with the thought of how their daughter was +blossoming into a well built and beautiful young lady. They became +quieter. Just from each other's glance and almost without knowing +it they agreed that it would soon be time to find a good man for +her. And, as if in confirmation of their new dreams and good +intentions, as soon as they reached their destination Grete was the +first to get up and stretch out her young body. + + + + + + +End of the Project Gutenberg EBook of Metamorphosis, by Franz Kafka +Translated by David Wyllie. + +*** END OF THIS PROJECT GUTENBERG EBOOK METAMORPHOSIS *** + +***** This file should be named 5200.txt or 5200.zip ***** +This and all associated files of various formats will be found in: + http://www.gutenberg.net/5/2/0/5200/ + + + +Updated editions will replace the previous one--the old editions +will be renamed. + +Creating the works from public domain print editions means that no +one owns a United States copyright in these works, so the Foundation +(and you!) can copy and distribute it in the United States without +permission and without paying copyright royalties. Special rules, +set forth in the General Terms of Use part of this license, apply to +copying and distributing Project Gutenberg-tm electronic works to +protect the PROJECT GUTENBERG-tm concept and trademark. Project +Gutenberg is a registered trademark, and may not be used if you +charge for the eBooks, unless you receive specific permission. If you +do not charge anything for copies of this eBook, complying with the +rules is very easy. You may use this eBook for nearly any purpose +such as creation of derivative works, reports, performances and +research. They may be modified and printed and given away--you may do +practically ANYTHING with public domain eBooks. Redistribution is +subject to the trademark license, especially commercial +redistribution. + + + +*** START: FULL LICENSE *** + +THE FULL PROJECT GUTENBERG LICENSE +PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK + +To protect the Project Gutenberg-tm mission of promoting the free +distribution of electronic works, by using or distributing this work +(or any other work associated in any way with the phrase "Project +Gutenberg"), you agree to comply with all the terms of the Full Project +Gutenberg-tm License (available with this file or online at +http://gutenberg.net/license). + + +Section 1. General Terms of Use and Redistributing Project Gutenberg-tm +electronic works + +1.A. By reading or using any part of this Project Gutenberg-tm +electronic work, you indicate that you have read, understand, agree to +and accept all the terms of this license and intellectual property +(trademark/copyright) agreement. If you do not agree to abide by all +the terms of this agreement, you must cease using and return or destroy +all copies of Project Gutenberg-tm electronic works in your possession. +If you paid a fee for obtaining a copy of or access to a Project +Gutenberg-tm electronic work and you do not agree to be bound by the +terms of this agreement, you may obtain a refund from the person or +entity to whom you paid the fee as set forth in paragraph 1.E.8. + +1.B. "Project Gutenberg" is a registered trademark. It may only be +used on or associated in any way with an electronic work by people who +agree to be bound by the terms of this agreement. There are a few +things that you can do with most Project Gutenberg-tm electronic works +even without complying with the full terms of this agreement. See +paragraph 1.C below. There are a lot of things you can do with Project +Gutenberg-tm electronic works if you follow the terms of this agreement +and help preserve free future access to Project Gutenberg-tm electronic +works. See paragraph 1.E below. + +1.C. The Project Gutenberg Literary Archive Foundation ("the Foundation" +or PGLAF), owns a compilation copyright in the collection of Project +Gutenberg-tm electronic works. Nearly all the individual works in the +collection are in the public domain in the United States. If an +individual work is in the public domain in the United States and you are +located in the United States, we do not claim a right to prevent you from +copying, distributing, performing, displaying or creating derivative +works based on the work as long as all references to Project Gutenberg +are removed. Of course, we hope that you will support the Project +Gutenberg-tm mission of promoting free access to electronic works by +freely sharing Project Gutenberg-tm works in compliance with the terms of +this agreement for keeping the Project Gutenberg-tm name associated with +the work. You can easily comply with the terms of this agreement by +keeping this work in the same format with its attached full Project +Gutenberg-tm License when you share it without charge with others. +This particular work is one of the few copyrighted individual works +included with the permission of the copyright holder. Information on +the copyright owner for this particular work and the terms of use +imposed by the copyright holder on this work are set forth at the +beginning of this work. + +1.D. The copyright laws of the place where you are located also govern +what you can do with this work. Copyright laws in most countries are in +a constant state of change. If you are outside the United States, check +the laws of your country in addition to the terms of this agreement +before downloading, copying, displaying, performing, distributing or +creating derivative works based on this work or any other Project +Gutenberg-tm work. The Foundation makes no representations concerning +the copyright status of any work in any country outside the United +States. + +1.E. Unless you have removed all references to Project Gutenberg: + +1.E.1. The following sentence, with active links to, or other immediate +access to, the full Project Gutenberg-tm License must appear prominently +whenever any copy of a Project Gutenberg-tm work (any work on which the +phrase "Project Gutenberg" appears, or with which the phrase "Project +Gutenberg" is associated) is accessed, displayed, performed, viewed, +copied or distributed: + +This eBook is for the use of anyone anywhere at no cost and with +almost no restrictions whatsoever. You may copy it, give it away or +re-use it under the terms of the Project Gutenberg License included +with this eBook or online at www.gutenberg.net + +1.E.2. If an individual Project Gutenberg-tm electronic work is derived +from the public domain (does not contain a notice indicating that it is +posted with permission of the copyright holder), the work can be copied +and distributed to anyone in the United States without paying any fees +or charges. If you are redistributing or providing access to a work +with the phrase "Project Gutenberg" associated with or appearing on the +work, you must comply either with the requirements of paragraphs 1.E.1 +through 1.E.7 or obtain permission for the use of the work and the +Project Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or +1.E.9. + +1.E.3. If an individual Project Gutenberg-tm electronic work is posted +with the permission of the copyright holder, your use and distribution +must comply with both paragraphs 1.E.1 through 1.E.7 and any additional +terms imposed by the copyright holder. Additional terms will be linked +to the Project Gutenberg-tm License for all works posted with the +permission of the copyright holder found at the beginning of this work. + +1.E.4. Do not unlink or detach or remove the full Project Gutenberg-tm +License terms from this work, or any files containing a part of this +work or any other work associated with Project Gutenberg-tm. + +1.E.5. Do not copy, display, perform, distribute or redistribute this +electronic work, or any part of this electronic work, without +prominently displaying the sentence set forth in paragraph 1.E.1 with +active links or immediate access to the full terms of the Project +Gutenberg-tm License. + +1.E.6. You may convert to and distribute this work in any binary, +compressed, marked up, nonproprietary or proprietary form, including any +word processing or hypertext form. However, if you provide access to or +distribute copies of a Project Gutenberg-tm work in a format other than +"Plain Vanilla ASCII" or other format used in the official version +posted on the official Project Gutenberg-tm web site (www.gutenberg.net), +you must, at no additional cost, fee or expense to the user, provide a +copy, a means of exporting a copy, or a means of obtaining a copy upon +request, of the work in its original "Plain Vanilla ASCII" or other +form. Any alternate format must include the full Project Gutenberg-tm +License as specified in paragraph 1.E.1. + +1.E.7. Do not charge a fee for access to, viewing, displaying, +performing, copying or distributing any Project Gutenberg-tm works +unless you comply with paragraph 1.E.8 or 1.E.9. + +1.E.8. You may charge a reasonable fee for copies of or providing +access to or distributing Project Gutenberg-tm electronic works provided +that + +- You pay a royalty fee of 20% of the gross profits you derive from + the use of Project Gutenberg-tm works calculated using the method + you already use to calculate your applicable taxes. The fee is + owed to the owner of the Project Gutenberg-tm trademark, but he + has agreed to donate royalties under this paragraph to the + Project Gutenberg Literary Archive Foundation. Royalty payments + must be paid within 60 days following each date on which you + prepare (or are legally required to prepare) your periodic tax + returns. Royalty payments should be clearly marked as such and + sent to the Project Gutenberg Literary Archive Foundation at the + address specified in Section 4, "Information about donations to + the Project Gutenberg Literary Archive Foundation." + +- You provide a full refund of any money paid by a user who notifies + you in writing (or by e-mail) within 30 days of receipt that s/he + does not agree to the terms of the full Project Gutenberg-tm + License. You must require such a user to return or + destroy all copies of the works possessed in a physical medium + and discontinue all use of and all access to other copies of + Project Gutenberg-tm works. + +- You provide, in accordance with paragraph 1.F.3, a full refund of any + money paid for a work or a replacement copy, if a defect in the + electronic work is discovered and reported to you within 90 days + of receipt of the work. + +- You comply with all other terms of this agreement for free + distribution of Project Gutenberg-tm works. + +1.E.9. If you wish to charge a fee or distribute a Project Gutenberg-tm +electronic work or group of works on different terms than are set +forth in this agreement, you must obtain permission in writing from +both the Project Gutenberg Literary Archive Foundation and Michael +Hart, the owner of the Project Gutenberg-tm trademark. Contact the +Foundation as set forth in Section 3 below. + +1.F. + +1.F.1. Project Gutenberg volunteers and employees expend considerable +effort to identify, do copyright research on, transcribe and proofread +public domain works in creating the Project Gutenberg-tm +collection. Despite these efforts, Project Gutenberg-tm electronic +works, and the medium on which they may be stored, may contain +"Defects," such as, but not limited to, incomplete, inaccurate or +corrupt data, transcription errors, a copyright or other intellectual +property infringement, a defective or damaged disk or other medium, a +computer virus, or computer codes that damage or cannot be read by +your equipment. + +1.F.2. LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right +of Replacement or Refund" described in paragraph 1.F.3, the Project +Gutenberg Literary Archive Foundation, the owner of the Project +Gutenberg-tm trademark, and any other party distributing a Project +Gutenberg-tm electronic work under this agreement, disclaim all +liability to you for damages, costs and expenses, including legal +fees. YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT +LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE +PROVIDED IN PARAGRAPH F3. YOU AGREE THAT THE FOUNDATION, THE +TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE +LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR +INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH +DAMAGE. + +1.F.3. LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a +defect in this electronic work within 90 days of receiving it, you can +receive a refund of the money (if any) you paid for it by sending a +written explanation to the person you received the work from. If you +received the work on a physical medium, you must return the medium with +your written explanation. The person or entity that provided you with +the defective work may elect to provide a replacement copy in lieu of a +refund. If you received the work electronically, the person or entity +providing it to you may choose to give you a second opportunity to +receive the work electronically in lieu of a refund. If the second copy +is also defective, you may demand a refund in writing without further +opportunities to fix the problem. + +1.F.4. Except for the limited right of replacement or refund set forth +in paragraph 1.F.3, this work is provided to you 'AS-IS,' WITH NO OTHER +WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +WARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE. + +1.F.5. Some states do not allow disclaimers of certain implied +warranties or the exclusion or limitation of certain types of damages. +If any disclaimer or limitation set forth in this agreement violates the +law of the state applicable to this agreement, the agreement shall be +interpreted to make the maximum disclaimer or limitation permitted by +the applicable state law. The invalidity or unenforceability of any +provision of this agreement shall not void the remaining provisions. + +1.F.6. INDEMNITY - You agree to indemnify and hold the Foundation, the +trademark owner, any agent or employee of the Foundation, anyone +providing copies of Project Gutenberg-tm electronic works in accordance +with this agreement, and any volunteers associated with the production, +promotion and distribution of Project Gutenberg-tm electronic works, +harmless from all liability, costs and expenses, including legal fees, +that arise directly or indirectly from any of the following which you do +or cause to occur: (a) distribution of this or any Project Gutenberg-tm +work, (b) alteration, modification, or additions or deletions to any +Project Gutenberg-tm work, and (c) any Defect you cause. + + +Section 2. Information about the Mission of Project Gutenberg-tm + +Project Gutenberg-tm is synonymous with the free distribution of +electronic works in formats readable by the widest variety of computers +including obsolete, old, middle-aged and new computers. It exists +because of the efforts of hundreds of volunteers and donations from +people in all walks of life. + +Volunteers and financial support to provide volunteers with the +assistance they need, is critical to reaching Project Gutenberg-tm's +goals and ensuring that the Project Gutenberg-tm collection will +remain freely available for generations to come. In 2001, the Project +Gutenberg Literary Archive Foundation was created to provide a secure +and permanent future for Project Gutenberg-tm and future generations. +To learn more about the Project Gutenberg Literary Archive Foundation +and how your efforts and donations can help, see Sections 3 and 4 +and the Foundation web page at http://www.pglaf.org. + + +Section 3. Information about the Project Gutenberg Literary Archive +Foundation + +The Project Gutenberg Literary Archive Foundation is a non profit +501(c)(3) educational corporation organized under the laws of the +state of Mississippi and granted tax exempt status by the Internal +Revenue Service. The Foundation's EIN or federal tax identification +number is 64-6221541. Its 501(c)(3) letter is posted at +http://pglaf.org/fundraising. Contributions to the Project Gutenberg +Literary Archive Foundation are tax deductible to the full extent +permitted by U.S. federal laws and your state's laws. + +The Foundation's principal office is located at 4557 Melan Dr. S. +Fairbanks, AK, 99712., but its volunteers and employees are scattered +throughout numerous locations. Its business office is located at +809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email +business@pglaf.org. Email contact links and up to date contact +information can be found at the Foundation's web site and official +page at http://pglaf.org + +For additional contact information: + Dr. Gregory B. Newby + Chief Executive and Director + gbnewby@pglaf.org + +Section 4. Information about Donations to the Project Gutenberg +Literary Archive Foundation + +Project Gutenberg-tm depends upon and cannot survive without wide +spread public support and donations to carry out its mission of +increasing the number of public domain and licensed works that can be +freely distributed in machine readable form accessible by the widest +array of equipment including outdated equipment. Many small donations +($1 to $5,000) are particularly important to maintaining tax exempt +status with the IRS. + +The Foundation is committed to complying with the laws regulating +charities and charitable donations in all 50 states of the United +States. Compliance requirements are not uniform and it takes a +considerable effort, much paperwork and many fees to meet and keep up +with these requirements. We do not solicit donations in locations +where we have not received written confirmation of compliance. To +SEND DONATIONS or determine the status of compliance for any +particular state visit http://pglaf.org + +While we cannot and do not solicit contributions from states where we +have not met the solicitation requirements, we know of no prohibition +against accepting unsolicited donations from donors in such states who +approach us with offers to donate. + +International donations are gratefully accepted, but we cannot make +any statements concerning tax treatment of donations received from +outside the United States. U.S. laws alone swamp our small staff. + +Please check the Project Gutenberg Web pages for current donation +methods and addresses. Donations are accepted in a number of other +ways including including checks, online payments and credit card +donations. To donate, please visit: http://pglaf.org/donate + + +Section 5. General Information About Project Gutenberg-tm electronic +works. + +Professor Michael S. Hart is the originator of the Project Gutenberg-tm +concept of a library of electronic works that could be freely shared +with anyone. For thirty years, he produced and distributed Project +Gutenberg-tm eBooks with only a loose network of volunteer support. + +Project Gutenberg-tm eBooks are often created from several printed +editions, all of which are confirmed as Public Domain in the U.S. +unless a copyright notice is included. Thus, we do not necessarily +keep eBooks in compliance with any particular paper edition. + +Most people start at our Web site which has the main PG search facility: + +http://www.gutenberg.net + +This Web site includes information about Project Gutenberg-tm, +including how to make donations to the Project Gutenberg Literary +Archive Foundation, how to help produce our new eBooks, and how to +subscribe to our email newsletter to hear about new eBooks. diff --git a/src/test/resources/The-Wizard-by-Rider-Haggard.txt b/src/test/resources/The-Wizard-by-Rider-Haggard.txt new file mode 100644 index 0000000..e2b1f8b --- /dev/null +++ b/src/test/resources/The-Wizard-by-Rider-Haggard.txt @@ -0,0 +1,6718 @@ +The Project Gutenberg EBook of The Wizard, by H. Rider Haggard + +This eBook is for the use of anyone anywhere at no cost and with +almost no restrictions whatsoever. You may copy it, give it away or +re-use it under the terms of the Project Gutenberg License included +with this eBook or online at www.gutenberg.org + + +Title: The Wizard + +Author: H. Rider Haggard + +Release Date: April 3, 2006 [EBook #2893 +Last Updated: September 23, 2016 + +Language: English + + +*** START OF THIS PROJECT GUTENBERG EBOOK THE WIZARD *** + + + + +Produced by John Bickers; Dagny + + + + + +THE WIZARD + +by H. Rider Haggard + + + + +DEDICATION + +To the Memory of the Child + +Nada Burnham, + +who “bound all to her” and, while her father cut his way through the +hordes of the Ingobo Regiment, perished of the hardships of war +at Buluwayo on 19th May, 1896, I dedicate these tales--and more +particularly the last, that of a Faith which triumphed over savagery and +death. + +H. Rider Haggard. + +Ditchingham. + + + +AUTHOR’S NOTE + +Of the three stories that comprise this volume[*], one, “The Wizard,” a +tale of victorious faith, first appeared some years ago as a Christmas +Annual. Another, “Elissa,” is an attempt, difficult enough owing to the +scantiness of the material left to us by time, to recreate the life of +the ancient Poenician Zimbabwe, whose ruins still stand in Rhodesia, +and, with the addition of the necessary love story, to suggest +circumstances such as might have brought about or accompanied its fall +at the hands of the surrounding savage tribes. The third, “Black Heart +and White Heart,” is a story of the courtship, trials and final union of +a pair of Zulu lovers in the time of King Cetywayo. + + [*] This text was prepared from a volume published in 1900 + titled “Black Heart and White Heart, and Other Stories.”-- + JB. + + + + + +THE WIZARD + + + +CHAPTER I + +THE DEPUTATION + +Has the age of miracle quite gone by, or is it still possible to the +Voice of Faith calling aloud upon the earth to wring from the dumb +heavens an audible answer to its prayer? Does the promise uttered by the +Master of mankind upon the eve of the end--“Whoso that believeth in Me, +the works that I do he shall do also . . . and whatsoever ye shall ask +in My name, that will I do;”--still hold good to such as do ask and do +believe? + +Let those who care to study the history of the Rev. Thomas Owen, and +of that strange man who carried on and completed his work, answer this +question according to their judgment. + +***** + +The time was a Sunday afternoon in summer, and the place a church in +the Midland counties. It was a beautiful church, ancient and spacious; +moreover, it had recently been restored at great cost. Seven or eight +hundred people could have found sittings in it, and doubtless they +had done so when Busscombe was a large manufacturing town, before the +failure of the coal supply and other causes drove away its trade. Now +it was much what it had been in the time of the Normans, a little +agricultural village with a population of 300 souls. Out of this +population, including the choir boys, exactly thirty-nine had elected to +attend church on this particular Sunday; and of these, three were fast +asleep and four were dozing. + +The Rev. Thomas Owen counted them from his seat in the chancel, for +another clergyman was preaching; and, as he counted, bitterness and +disappointment took hold of him. The preacher was a “Deputation,” sent +by one of the large missionary societies to arouse the indifferent to +a sense of duty towards their unconverted black brethren in Africa, and +incidentally to collect cash to be spent in the conversion of the +said brethren. The Rev. Thomas Owen himself suggested the visit of the +Deputation, and had laboured hard to secure him a good audience. But +the beauty of the weather, or terror of the inevitable subscription, +prevailed against him. Hence his disappointment. + +“Well,” he thought, with a sigh, “I have done my best, and I must make +it up out of my own pocket.” + +Then he settled himself to listen to the sermon. + +The preacher, a battered-looking individual of between fifty and sixty +years of age, was gaunt with recent sickness, patient and unimaginative +in aspect. He preached extemporarily, with the aid of notes; and it +cannot be said that his discourse was remarkable for interest, at any +rate in its beginning. Doubtless the sparse congregation, so prone to +slumber, discouraged him; for offering exhortations to empty benches is +but weary work. Indeed he was meditating the advisability of bringing +his argument to an abrupt conclusion when, chancing to glance round, he +became aware that he had at least one sympathetic listener, his host, +the Rev. Thomas Owen. + +From that moment the sermon improved by degrees, till at length it +reached a really high level of excellence. Ceasing from rhetoric, the +speaker began to tell of his own experience and sufferings in the Cause +amongst savage tribes; for he himself was a missionary of many years +standing. He told how once he and a companion had been sent to a +nation, who named themselves the Sons of Fire because their god was the +lightning, if indeed they could be said to boast any gods other than +the Spear and the King. In simple language he narrated his terrible +adventures among these savages, the murder of his companion by command +of the Council of Wizards, and his own flight for his life; a tale +so interesting and vivid that even the bucolic sleepers awakened and +listened open-mouthed. + +“But this is by the way,” he went on; “for my Society does not ask you +to subscribe towards the conversion of the Children of Fire. Until that +people is conquered--which very likely will not be for generations, +seeing that they live in Central Africa, occupying a territory that +white men do not desire--no missionary will dare again to visit them.” + +At this moment something caused him to look a second time at Thomas +Owen. He was leaning forward in his place listening eagerly, and a +strange light filled the large, dark eyes that shone in the pallor of +his delicate, nervous face. + +“There is a man who would dare, if he were put to it,” thought the +Deputation to himself. Then he ended his sermon. + +That evening the two men sat at dinner in the rectory. It was a very +fine rectory, beautifully furnished; for Owen was a man of taste which +he had the means to gratify. Also, although they were alone, the dinner +was good--so good that the poor broken-down missionary, sipping his +unaccustomed port, a vintage wine, sighed aloud in admiration and +involuntary envy. + +“What is the matter?” asked Owen. + +“Nothing, Mr. Owen;” then, of a sudden thawing into candour, he +added: “that is, everything. Heaven forgive me; but I, who enjoy your +hospitality, am envious of you. Don’t think too hardly of me; I have a +large family to support, and if only you knew what a struggle my life +is, and has been for the last twenty years, you would not, I am sure. +But you have never experienced it, and could not understand. ‘The +labourer is worthy of his hire.’ Well, my hire is under two hundred a +year, and eight of us must live--or starve--on it. And I have worked, +ay, until my health is broken. A labourer indeed! I am a very hodman, a +spiritual Sisyphus. And now I must go back to carry my load and roll +my stone again and again among those hopeless savages till I die of +it--till I die of it!” + +“At least it is a noble life and death!” exclaimed Owen, a sudden fire +of enthusiasm burning in his dark eyes. + +“Yes, viewed from a distance. Were you asked to leave this living of two +thousand a year--I see that is what they put it at in Crockford--with +its English comforts and easy work, that _you_ might lead that life and +attain that death, then you would think differently. But why should +I bore you with such talk? Thank Heaven that your lines are cast in +pleasant places. Yes, please, I will take one more glass; it does me +good.” + +“Tell me some more about that tribe you were speaking of in your sermon, +the ‘Sons of Fire’ I think you called them,” said Owen, as he passed him +the decanter. + +So, with an eloquence induced by the generous wine and a quickened +imagination, the Deputation told him--told him many strange things and +terrible. For this people was an awful people: vigorous in mind +and body, and warriors from generation to generation, but +superstition-ridden and cruel. They lived in the far interior, some +months’ journey by boat and ox-waggon from the coast, and of white men +and their ways they knew but little. + +“How many of them are there?” asked Owen. + +“Who can say?” he answered. “Nearly half-a-million, perhaps; at least +they pretend that they can put sixty thousand men under arms.” + +“And did they treat you badly when you first visited them?” + +“Not at first. They received us civilly enough; and on a given day we +were requested to explain to the king and the Council of Wizards the +religion which we came to teach. All that day we explained and all +the next--or rather my friend did, for I knew very little of the +language--and they listened with great interest. At last the chief of +the wizards and the first prophet to the king rose to question us. He +was named Hokosa, a tall, thin man, with a spiritual face and terrible +calm eyes. + +“‘You speak well, son of a White Man,’ he said, ‘but let us pass from +words to deeds. You tell us that this God of yours, whom you desire that +we should take as our God, so that you may become His chief prophets in +the land, was a wizard such as we are, though grater than we are; for +not only did He know the past and the future as we do, but also He could +cure those who were smitten with hopeless sickness, and raise those +who were dead, which we cannot do. You tell us, moreover, that by faith +those who believe on Him can do works as great as He did, and that you +do believe on Him. Therefore we will put you to the proof. Ho! there, +lead forth that evil one.’ + +“As he spoke a man was placed before us, one who had been convicted of +witchcraft or some other crime. + +“‘Kill him!’ said Hokosa. + +“There was a faint cry, a scuffle, a flashing of spears, and the man lay +still before us. + +“‘Now, followers of the new God,’ said Hokosa, ‘raise him from the dead +as your Master did!’ + +“In vain did we offer explanations. + +“‘Peace!’ said Hokosa at length, ‘your words weary us. Look now, either +you have preached to us a false god and are liars, or you are traitors +to the King you preach, since, lacking faith in Him, you cannot do such +works as He gives power to do to those who have faith in Him. Out of +your own mouths are you judged, White Men. Choose which horn of the bull +you will, you hang to one of them, and it shall pierce you. This is +the sentence of the king, I speak it who am the king’s mouth: That you, +White Man, who have spoken to us and cheated us these two weary days, +be put to death, and that you, his companion who have been silent, be +driven from the land.’ + +“I can hardly bear to tell the rest of it, Mr. Owen. They gave my poor +friend ten minutes to ‘talk to his Spirit,’ then they speared him before +my face. After it was over, Hokosa spoke to me, saying:-- + +“‘Go back, White Man, to those who sent you, and tell them the words of +the Sons of Fire: That they have listened to the message of peace, +and though they are a people of warriors, yet they thank them for that +message, for in itself it sounds good and beautiful in their ears, if it +be true. Tell them that having proved you liars, they dealt with you as +all honest men seek that liars should be dealt with. Tell them that they +desire to hear more of this matter, and if one can be sent to them who +has no false tongue; who in all things fulfills the promises of his +lips, that they will hearken to him and treat him well, but that for +such as you they keep a spear.’” + +“And who went after you got back?” asked Owen, who was listening with +the deepest interest. + +“Who went? Do you suppose that there are many mad clergymen in Africa, +Mr. Owen? Nobody went.” + +“And yet,” said Owen, speaking more to himself than to his guest, “the +man Hokosa was right, and the Christian who of a truth believes the +promises of our religion should trust to them and go.” + +“Then perhaps you would like to undertake the mission, Mr. Owen,” said +the Deputation briskly; for the reflection stung him, unintentional as +it was. + +Owen started. + +“That is a new idea,” he said. “And now perhaps you wish to go to bed; +it is past eleven o’clock.” + + + +CHAPTER II + +THOMAS OWEN + +Thomas Owen went to his room, but not to bed. Taking a Bible from the +table, he consulted reference after reference. + +“The promise is clear,” he said aloud presently, as he shut the +book; “clear and often repeated. There is no escape from it, and no +possibility of a double meaning. If it is not true, then it would seem +that nothing is true, and that every Christian in the world is tricked +and deluded. But if it _is_ true, why do we never hear of miracles? +The answer is easy: Because we have not faith enough to work them. The +Apostles worked miracles; for they had seen, therefore their faith was +perfect. Since their day nobody’s faith has been quite perfect; at least +I think not. The physical part of our nature prevents it. Or perhaps the +miracles still happen, but they are spiritual miracles.” + +Then he sat down by the open window, and gazing at the dreamy beauty of +the summer night, he thought, for his soul was troubled. Once before +it had been troubled thus; that was nine years ago, for now he was but +little over thirty. Then a call had come to him, a voice had seemed to +speak to his ears bidding him to lay down great possessions to follow +whither Heaven should lead him. Thomas Owen had obeyed the voice; +though, owing to circumstances which need not be detailed, to do so he +was obliged to renounce his succession to a very large estate, and to +content himself with a younger son’s portion of thirty thousand pounds +and the reversion to the living which he had now held for some five +years. + +Then and there, with singular unanimity and despatch, his relations came +to the conclusion that he was mad. To this hour, indeed, those who stand +in his place and enjoy the wealth and position that were his by right, +speak of him as “poor Thomas,” and mark their disapprobation of his +peculiar conduct by refusing with an unvarying steadiness to subscribe +even a single shilling to a missionary society. How “poor Thomas” speaks +of them in the place where he is we may wonder, but as yet we cannot +know--probably with the gentle love and charity that marked his every +action upon earth. But this is by the way. + +He had entered the Church, but what had he done in its shadow? This was +the question which Owen asked himself as he sat that night by the open +window, arraigning his past before the judgment-seat of conscience. For +three years he had worked hard somewhere in the slums; then this living +had fallen to him. He had taken it, and from that day forward his record +was very much of a blank. The parish was small and well ordered; there +was little to do in it, and the Salvation Army had seized upon and +reclaimed two of the three confirmed drunkards it could boast. + +His guest’s saying echoed in his brain like the catch of a tune--“that +_you_ might lead that life and attain that death.” Supposing that +he were bidden so to do now, this very night, would he indeed “think +differently”? He had become a priest to serve his Maker. How would it be +were that Maker to command that he should serve Him in this extreme and +heroic fashion? Would he flinch from the steel, or would he meet it as +the martyrs met it of old? + +Physically he was little suited to such an enterprise, for in appearance +he was slight and pale, and in constitution delicate. Also, there was +another reason against the thing. High Church and somewhat ascetic in +his principles, in the beginning he had admired celibacy, and in secret +dedicated himself to that state. But at heart Thomas was very much a +man, and of late he had come to see that which is against nature is +presumably not right, though fanatics may not hesitate to pronounce +it wrong. Possibly this conversion to more genial views of life was +quickened by the presence in the neighbourhood of a young lady whom +he chanced to admire; at least it is certain that the mere thought of +seeing her no more for ever smote him like a sword of sudden pain. + +***** + +That very night--or so it seemed to him, and so he believed--the Angel +of the Lord stood before him as he was wont to stand before the men of +old, and spoke a summons in his ear. How or in what seeming that summons +came Thomas Owen never told, and we need not inquire. At the least he +heard it, and, like the Apostles, he arose and girded his loins to obey. +For now, in the hour of trial, it proved that this man’s faith partook +of the nature of their faith. It was utter and virgin; it was not +clogged with nineteenth-century qualifications; it had never dallied +with strange doctrines, or kissed the feet of pinchbeck substitutes for +God. In his heart he believed that the Almighty, without intermediary, +but face to face, had bidden him to go forth into the wilderness there +to perish. So he bowed his head and went. + +On the following morning at breakfast Owen had some talk with his friend +the Deputation. + +“You asked me last night,” he said quietly, “whether I would undertake +a mission to that people of whom you were telling me--the Sons of Fire. +Well, I have been thinking it over, and come to the conclusion that I +will do so----” + +At this point the Deputation, concluding that his host must be mad, +moved quietly but decidedly towards the door. + +“Wait a moment,” went on Owen, in a matter-of-fact voice, “the dog-cart +will not be round for another three-quarters of an hour. Tell me, if it +were offered to you, and on investigation you proved suitable, would you +care to take over this living?” + +“Would I care to take over this living?” gasped the astonished +Deputation. “Would I care to walk down that garden and find myself in +Heaven? But why are you making fun of me?” + +“I am not making fun of you. If I go to Africa I must give up the +living, of which I own the advowson, and it occurred to me that it might +suit you--that is all. You have done your share; your health is broken, +and you have many dependent upon you. It seems right, therefore, that +you should rest, and that I should work. If I do no good yonder, at the +least you and yours will be a little benefited.” + +***** + +That same day Owen chanced to meet the lady who has been spoken of as +having caught his heart. He had meant to go away without seeing her, but +fortune brought them together. Hitherto, whilst in reality leading him +on, she had seemed to keep him at a distance, with the result that he +did not know that it was her fixed intention to marry him. To her, +with some hesitation, he told his plans. Surprised and frightened into +candour, the lady reasoned with him warmly, and when reason failed to +move him she did more. By some subtle movement, with some sudden word, +she lifted the veil of her reserve and suffered him to see her heart. +“If you will not stay for aught else,” said her troubled eyes, “then, +love, stay for me.” + +For a moment he was shaken. Then he answered the look straight out, as +was his nature. + +“I never guessed,” he said. “I did not presume to hope--now it is too +late! Listen! I will tell you what I have told no living soul, though +thereafter you may think me mad. Weak and humble as I am, I believe +myself to have received a Divine mission. I believe that I shall execute +it, or bring about its execution, but at the ultimate cost of my own +life. Still, in such a service two are better than one. If you--can care +enough--if you----” + +But the lady had already turned away, and was murmuring her farewell in +accents that sounded like a sob. Love and faith after this sort were not +given to her. + +Of all Owen’s trials this was the sharpest. Of all his sacrifices this +was the most complete. + + + +CHAPTER III + +THE TEMPTATION + +Two years have gone by all but a few months, and from the rectory in a +quiet English village we pass to a scene in Central, or South Central, +Africa. + +On the brow of a grassy slope dotted over with mimosa thorns, and close +to a gushing stream of water, stands a house, or rather a hut, built +of green brick and thatched with grass. Behind this hut is a fence of +thorns, rough but strong, designed to protect all within it from the +attacks of lions and other beasts of prey. At present, save for a +solitary mule eating its provender by the wheel of a tented ox-waggon, +it is untenanted, for the cattle have not yet been kraaled for the +night. Presently Thomas Owen enters this enclosure by the back door of +the hut, and having attended to the mule, which whinnies at the sight +of him, goes to the gate and watches there till he sees his native boys +driving the cattle up the slope of the hill. At length they arrive, and +when he has counted them to make sure that none are missing, and in a +few kind words commended the herds for their watchfulness, he walks +to the front of the house and, seating himself upon a wooden stool set +under a mimosa tree that grows near the door, he looks earnestly towards +the west. + +The man has changed somewhat since last we saw him. To begin with, he +has grown a beard, and although the hot African sun has bronzed it +into an appearance of health, his face is even thinner than it was, and +therein the great spiritual eyes shine still more strangely. + +At the foot of the slope runs a wide river, just here broken into rapids +where the waters make an angry music. Beyond this river stretches a +vast plain bounded on the horizon by mountain ranges, each line of them +rising higher than the other till their topmost and more distant peaks +melt imperceptibly into the tender blue of the heavens. This is the land +of the Sons of Fire, and yonder amid the slopes of the nearest hills is +the great kraal of their king, Umsuka, whose name, being interpreted, +means The Thunderbolt. + +In the very midst of the foaming rapids, and about a thousand yards +from the house lies a space of rippling shallow water, where, unless it +chances to be in flood, the river can be forded. It is this ford that +Owen watches so intently. + +“John should have been back twelve hours ago,” he mutters to himself. “I +pray that no harm has befallen him at the Great Place yonder.” + +Just then a tiny speck appears far away on the plain. It is a man +travelling towards the water at a swinging trot. Going into the hut, +Owen returns with a pair of field-glasses, and through them scrutinises +the figure of the man. + +“Heaven be praised! It is John,” he mutters, with a sigh of relief. +“Now, I wonder what answer he brings?” + +Half an hour later John stands before him, a stalwart native of the +tribe of the Amasuka, the People of Fire, and with uplifted hand salutes +him, giving him titles of honour. + +“Praise me not, John,” said Owen; “praise God only, as I have taught you +to do. Tell me, have you seen the king, and what is his word?” + +“Father,” he answered, “I journeyed to the great town, as you bade me, +and I was admitted before the majesty of the king; yes, he received me +in the courtyard of the House of Women. With his guards, who stood at +a distance out of hearing, there were present three only; but oh! those +three were great, the greatest in all the land after the king. They were +Hafela, the king that is to come, the prince Nodwengo, his brother, and +Hokosa the terrible, the chief of the wizards; and I tell you, father, +that my blood dried up and my heart shrivelled when they turned their +eyes upon me, reading the thoughts of my heart.” + +“Have I not told you, John, to trust in God, and fear nothing at the +hands of man?” + +“You told me, father, but still I feared,” answered the messenger +humbly. “Yet, being bidden to it, I lifted my forehead from the dust +and stood upon my feet before the king, and delivered to him the message +which you set between my lips.” + +“Repeat the message, John.” + +“‘O King,’ I said, ‘beneath those footfall the whole earth shakes, whose +arms stretch round the world and whose breath is the storm, I, whose +name is John, am sent by the white man whose name is Messenger’--for by +that title you bade me make you known--‘who for a year has dwelt in the +land that your spears have wasted beyond the banks of the river. These +are the words which he spoke to me, O King, that I pass on to you with +my tongue: “To the King Umsuka, lord of the Amasuka, the Sons of Fire, +I, Messenger, who am the servant and the ambassador of the King of +Heaven, give greeting. A year ago, King, I sent to you saying that the +message which was brought by that white man whom you drove from your +land had reached the ears of Him whom I serve, the High and Holy One, +and that, speaking in my heart, He had commanded me to take up the +challenge of your message. Here am I, therefore, ready to abide by the +law which you have laid down; for if guile or lies be found in me, then +let me travel from your land across the bridge of spears. Still, I would +dwell a little while here where I am before I pass into the shadow of +your rule and speak in the ears of your people as I have been bidden. +Know, King, that first I would learn your tongue, and therefore I demand +that one of your people may be sent to dwell with me and to teach me +that tongue. King, you heard my words and you sent me a man to dwell +with me, and that man has taught me your tongue, and I also have taught +him, converting him to my faith and giving him a new name, the name of +John. King, now I seek your leave to visit you, and to deliver into your +ears the words with which I, Messenger, am charged. I have spoken.”’ + +“Thus I, John, addressed the great ones, my father, and they listened +in silence. When I had done they spoke together, a word here and a word +there. Then Hokosa, the king’s mouth, answered me, telling the thought +of the king: ‘You are a bold man, you whose name is John, but who once +had another name--you, my servant, who dare to appear before me, and to +make it known to me that you have been turned to a new faith and serve +another king than I. Yet because you are bold, I forgive you. Go back +now to that white man who is named Messenger and who comes upon an +embassy to me from the Lord of Heaven, and bid him come in peace. Yet +warn him once again that here also we know something of the Powers that +are not seen, here also we have our wizards who draw wisdom from the +air, who tame the thunderbolt and compel the rain, and that he must +show himself greater than all of these if he would not pass hence by the +bridge of spears. Let him, therefore, take counsel with his heart and +with Him he serves, if such a One there is, and let him come or let him +stay away as it shall please him.’” + +“So be it,” said Owen; “the words of the king are good, and to-morrow we +will start for the Great Place.” + +John heard and assented, but without eagerness. + +“My father,” he said, in a doubtful and tentative voice, “would it not +perhaps be better to bide here awhile first?” + +“Why?” asked Owen. “We have sown, and now is the hour to reap.” + +“It is so, my father, but as I ran hither, full of the king’s words, it +came into my mind that now is not the time to convert the Sons of Fire. +There is trouble brewing at the Great Palace, father. Listen, and I will +tell you; as I have heard, so I will tell you. You know well that our +King Umsuka has two sons, Hafela and Nodwengo; and of these Hafela is +the heir-apparent, the fruit of the chief wife of the king, and Nodwengo +is sprung from another wife. Now Hafela is proud and cruel, a warrior of +warriors, a terrible man, and Nodwengo is gentle and mild, like to his +mother whom the king loves. Of late it has been discovered that Hafela, +weary of waiting for power, has made a plot to depose his father and to +kill Nodwengo, his brother, so that the land and those who dwell in it +may become his without question. This plot the king knows--I had it from +one of his women, who is my sister--and he is very wroth, yet he dare do +little, for he grows old and timid, and seeks rest, not war. Yet he is +minded, if he can find the heart, to go back upon the law and to +name Nodwengo as his heir before all the army at the feast of the +first-fruits, which shall be held on the third day from to-night. This +Hafela knows, and Nodwengo knows it also, and each of them has summoned +his following, numbering thousands and tens of thousands of spears, to +attend this feast of the first-fruits. That feast may well be a feast +of vultures, my father, and when the brothers and their regiments rush +together fighting for the throne, what will chance to the white man who +comes at such a moment to preach a faith of peace, and to his servant, +one John, who led him there?” + +“I do not know,” answered Owen, “and it troubles me not at all. I go to +carry out my mission, and in this way or in that it will be carried out. +John, if you are fearful or unbelieving leave me to go alone.” + +“Nay, father, I am not fearful; yet, father, I would have you +understand. Yonder there are men who can work wizardry. _Wow!_ I know, +for I have seen it, and they will demand from you magic greater than +their magic.” + +“What of it, John?” + +“Only this, my father, that if they ask and you fail to give, they will +kill you. You teach beautiful things, but say, are you a wizard? When +the child of a woman yonder lay dead, you could not raise it as did the +Christ; when the oxen were sick with the pest, you could not cure them; +or at least, my father, you did not, although you wept for the child and +were sorry at the loss of the oxen. Now, my father, if perchance +they ask you to do such things as these yonder, or die, say what will +happen?” + +“One of two things, John: either I shall die or I shall do the things.” + +“But”--hesitated John--“surely you do not believe that----” and he broke +off. + +Owen turned round and looked at his disciple with kindling eyes. “I do +believe, O you of little faith!” he said. “I do believe that yonder I +have a mission, and that He Whom I serve will give me power to carry +out that mission. You are right, I can work no miracles; but He can work +miracles Whom everything in heaven and earth obeys, and if there is need +He will work them through me, His instrument. Or perhaps He will +not work them, and I shall die, because thus His ends will best be +forwarded. At the least I go in faith, fearing nothing, for what has he +to fear who knows the will of God and does it? But to you who doubt, I +say--leave me!” + +The man spread out his hands in deprecation; his thick lips trembled a +little, and something like a tear appeared at the corners of his eyes. + +“Father,” he said, “am I a coward that you should talk to me thus? +I, who for twenty years have been a soldier of my king and for ten a +captain in my regiment? These scars show whether or no I am a coward,” + and he pointed to his breast, “but of them I will not speak. I am no +coward, else I had not gone upon that errand of yours. Why, then, should +you reproach me because my ears are not so open as yours, as my heart +has not understanding? I worship that God of Whom you have taught me, +but He never speaks to me as He does to you. I never meet Him as I walk +at night; He leaves me quite alone. Therefore it is that I fear that +when the hour of trial comes He may desert you; and unless He covers you +with His shield, of this I am sure, that the spear is forged which shall +blush red in your heart, my father. It is for you that I fear, who are +so gentle and tender; not for myself, who am well accustomed to look in +the eyes of Death, and who expect no more than death.” + +“Forgive me,” said Owen hastily, for he was moved; “and be sure that +the shield will be over us till the time comes for us to pass whither we +shall need none.” + +***** + +That night Owen rose from the task at which he was labouring slowly and +painfully--a translation of passages from the Gospel of St. John into +the language of the Amasuka--and going to the open window-place of the +hut, he rested his elbows upon it and thought, staring with empty eyes +into the blackness of the night. Now it was as he sat thus that a great +agony of doubt took possession of his soul. The strength which hitherto +had supported him seemed to be withdrawn, and he was left, as John +had said, “quite alone.” Strange voices seemed to whisper in his ears, +reproaching and reviling him; temptations long ago trampled under foot +rose again in might, alluring him. + +“Fool,” said the voices, “get you hence before it is too late. You have +been mad; you who dreamed that for your sake, to satisfy your pride, the +Almighty will break His silence and strain His law. Are you then better, +or greater, or purer than millions who have gone before you, that for +you and you alone this thing should be done? Why, were it not that you +are mad, you would be among the chief of sinners; you who dare to ask +that the Powers of Heaven should be set within your feeble hand, that +the Angels of Heaven should wait upon your mortal breath. Worm that you +are, has God need of such as you? If it is His will to turn the heart +of yonder people He will do it, but not by means of _you_. You and the +servant whom you are deluding to his death will perish miserably, and +this alone shall be the fruit of your presumptuous sin. Get you back out +of this wilderness before the madness takes you afresh. You are still +young, you have wealth; look where She stands yonder whom you desire. +Get you back, and forget your folly in her arms.” + +These thoughts, and many others of like nature, tore Owen’s soul in +that hour of strange and terrible temptation. He seemed to see himself +standing before the thousands of the savage nation he went to save, and +to hear the mocking voices of their witch-finders commanding him, if he +were a true man and the servant of that God of Whom he prated, to +give them a sign, only a little sign; perhaps to move a stone without +touching it with his hand, or to cause a dead bough to blossom. + +Then he would beseech Heaven with frantic prayers, and in vain, till at +length, amidst a roar of laughter, he, the false prophet and the liar, +was led out to his doom. He saw the piteous wondering look of the +believer whom he had betrayed to death; he saw the fierce faces and +the spears on high. Seeing all this his spirit broke, and, just as the +little clock in the room behind him struck the first stroke of midnight, +with a great and bitter cry to God to give him back the faith and +strength that he had lost, Owen’s head fell forward and he sank into a +swoon there upon the window-place. + + + +CHAPTER IV + +THE VISION + +Was it swoon or sleep? + +At least it seemed to Owen that presently once again he was gazing into +the dense intolerable blackness of the night. Then a marvel came to +pass, for the blackness opened, or rather on it, framed and surrounded +by it, there appeared a vision. It was the vision of a native town, +having a great bare space in the centre of it encircled by hundreds or +thousands of huts. But there was no one stirring about the huts, for +it was night--not this his night of trial indeed, since now the sky was +strewn with innumerable stars. Everything was silent about that town, +save that now and again a dog barked or a fretful child wailed within +a hut, or the sentries as they passed saluted each other in the name of +the king. + +Among all those hundreds of huts, to Owen it seemed that his attention +was directed to one which stood apart surrounded with a fence. Now the +interior of the hut opened itself to him. It was not lighted, yet with +his spirit sense he could see its every detail: the polished floor, the +skin rugs, the beer gourds, the shields and spears, the roof-tree of red +wood, and the dried lizard hanging from the thatch, a charm to ward off +evil. In this hut, seated face to face halfway between the centre-post +and the door-hole, were two men. The darkness was deep about them, and +they whispered to each other through it; but in his dream this was no +bar to Owen’s sight. He could discern their faces clearly. + +One of them was that of a man of about thirty-five years of age. In +stature he was almost a giant. He wore a kaross of leopard skins, and on +his wrists and ankles were rings of ivory, the royal ornaments. His face +was fierce and powerful; his eyes, which were set far apart, rolled +so much that at times they seemed all white; and his fingers played +nervously with the handle of a spear that he carried in his right hand. +His companion was of a different stamp; a person of more than fifty +years, he was tall and spare in figure, with delicately shaped hands +and feet. His hair and little beard were tinged with grey, his face was +strikingly handsome, nervous and expressive, and his forehead both broad +and high. But more remarkable still were his eyes, which shone with a +piercing brightness, almost grey in colour, steady as the flame of a +well-trimmed lamp, and so cold that they might have been precious stones +set in the head of a statue. + +“Must I then put your thoughts in words?” said this man in a clear +quick whisper. “Well, so be it; for I weary of sitting here in the dark +waiting for water that will not flow. Listen, Prince; you come to talk +to me of the death of a king--is it not so? Nay do not start. Why are +you affrighted when you hear upon the lips of another the plot that +these many months has been familiar to your breast?” + +“Truly, Hokosa, you are the best of wizards, or the worst,” answered +the great man huskily. “Yet this once you are mistaken,” he added with a +change of voice. “I came but to ask you for a charm to turn my father’s +heart----” + +“To dust? Prince, if I am mistaken, why am I the best of wizards, or the +worst, and why did your jaw drop and your face change at my words, and +why do you even now touch your dry lips with your tongue? Yes, I know +that it is dark here, yet some can see in it, and I am one of them. Ay, +Prince, and I can see your mind also. You would be rid of your father: +he has lived too long. Moreover his love turns to Nodwengo, the good and +gentle; and perhaps--who can say?--it is even in his thought, when all +his regiments are about him two days hence, to declare that you, Prince, +are deposed, and that your brother, Nodwengo, shall be king in your +stead. Now, Nodwengo you cannot kill; he is too well loved and too well +guarded. If he died suddenly, his dead lips would call out ‘Murder!’ in +the ears of all men; and, Prince, all eyes would turn to you, who alone +could profit by his end. But if the king should chance to die--why he is +old, is he not? and such things happen to the old. Also he grows feeble, +and will not suffer the regiments to be doctored for war, although day +by day they clamour to be led to battle; for he seeks to end his years +in peace.” + +“I say that you speak folly,” answered the prince with vehemence. + +“Then, Son of the Great One, why should you waste time in listening to +me? Farewell, Hafela the Prince, first-born of the king, who in a day to +come shall carry the shield of Nodwengo; for he is good and gentle, and +will spare your life--if I beg it of him.” + +Hafela stretched out his hand through the darkness, and caught Hokosa by +the wrist. + +“Stay,” he whispered, “it is true. The king must die; for if he does not +die within three days, I shall cease to be his heir. I know it through +my spies. He is angry with me; he hates me, and he loves Nodwengo +and the mother of Nodwengo. But if he dies before the last day of the +festival, then that decree will never pass his lips, and the regiments +will never roar out the name of Nodwengo as the name of the king to +come. He must die, I tell you, Hokosa, and--by your hand.” + +“By _my_ hand, Prince! Nay; what have you to offer me in return for such +a deed as this? Have I not grown up in Umsuka’s shadow, and shall I cut +down the tree that shades me?” + +“What have I to offer you? This: that next to myself you shall be the +greatest in the land, Hokosa.” + +“That I am already, and whoever rules it, that I must always be. I, who +am the chief of wizards; I, the reader of men’s hearts; I, the hearer +of men’s thoughts! I, the lord of the air and the lightning; I, the +invulnerable. If you would murder, Prince, then do the deed; do it +knowing that I have your secret, and that henceforth you who rule shall +be my servant. Nay, you forget that I can see in the dark; lay down that +assegai, or, by my spirit, prince as you are, I will blast you with a +spell, and your body shall be thrown to the kites, as that of one who +would murder his king and father!” + +The prince heard and shook, his cheeks sank in, the muscles of his great +form seemed to collapse, and he grovelled on the floor of the hut. + +“I know your magic,” he groaned; “use it for me, not against me! What +is there that I can offer you, who have everything except the throne, +whereon you cannot sit, seeing that you are not of the blood-royal?” + +“Think,” said Hokosa. + +For a while the prince thought, till presently his form straightened +itself, and with a quick movement he lifted up his head. + +“Is it, perchance, my affianced wife?” he whispered; “the lady Noma, +whom I love, and who, according to our custom, I shall wed as the queen +to be after the feast of first-fruits? Oh! say it not, Hokosa.” + +“I say it,” answered the wizard. “Listen, Prince. The lady Noma is the +only child of my blood-brother, my friend, with whom I was brought up, +he who was slain at my side in the great war with the tribes of the +north. She was my ward: she was more; for through her--ah! you know +not how--I held my converse with the things of earth and air, the very +spirits that watch us now in this darkness, Hafela. Thus it happened, +that before ever she was a woman, her mind grew greater than the mind +of any other woman, and her thought became my thought, and my thought +became her thought, for I and no other am her master. Still I waited +to wed her till she was fully grown; and while I waited I went upon an +embassy to the northern tribes. Then it was that you saw the maid in +visiting at my kraal, and her beauty and her wit took hold of you; and +in the council of the king, as you have a right to do, you named her as +your head wife, the queen to be. + +“The king heard and bowed his head; he sent and took her, and placed her +in the House of the Royal Women, there to abide till this feast of the +first-fruits, when she shall be given to you in marriage. Yes, he sent +her to that guarded house wherein not even I may set my foot. Although +I was afar, her spirit warned me, and I returned, but too late; for she +was sealed to you of the blood-royal, and that is a law which may not be +broken. + +“Hafela, I prayed you to return her to me, and you mocked me. I would +have brought you to your death, but it could not have availed me: for +then, by that same law, which may not be broken, she who was sealed to +you must die with you; and though thereafter her spirit would sit with +me till I died also, it was not enough, since I who have conquered all, +yet cannot conquer the fire that wastes my heart, nor cease to long by +night and day for a woman who is lost to me. Then it was, Hafela, that +I plotted vengeance against you. I threw my spell over the mind of the +king, till he learnt to hate you and your evil deeds; and I, even I, +have brought it about that your brother should be preferred before you, +and that you shall be the servant in his house. This is the price that +you must pay for her of whom you have robbed me; and by my spirit and +her spirit you shall pay! Yet listen. Hand back the girl, as you may +do--for she is not yet your wife--and choose another for your queen, and +I will undo all that I have done, and I will find you a means, Hafela, +to carry out your will. Ay, before six suns have set, the regiments +rushing past you shall hail you King of the Nation of the Amasuka, Lord +of the ancient House of Fire!” + +“I cannot,” groaned the prince; “death were better than this!” + +“Ay, death were better; but you shall not die, you shall live a servant, +and your name shall become a mockery, a name for women to make rhymes +on.” + +Now the prince sprang up. + +“Take her!” he hissed; “take her! you, who are an evil ghost; you, +beneath whose eyes children wail, and at whose passing the hairs on the +backs of hounds stand up! Take her, priest of death and ill; but take my +curse with her! Ah! I also can prophecy; and I tell you that this +woman whom you have taught, this witch of many spells, whose glance can +shrivel the hearts of men, shall give you to drink of your own medicine; +ay, she shall dog you to the death, and mock you while you perish by an +end of shame!” + +“What,” laughed the wizard, “have I a rival in my own arts? Nay, Hafela, +if you would learn the trade, pay me well and I will give you lessons. +Yet I counsel you not; for you are flesh, nothing but flesh, and he who +would rule the air must cultivate the spirit. Why, I tell you, Prince, +that even the love for her who is my heart, the lady whom we both would +wed, partaking of the flesh as, alas! it does, has cost me half my +powers. Now let us cease from empty scoldings, and strike our bargain. + +“Listen. On the last day of the feast, when all the regiments are +gathered to salute the king there in his Great Place according to +custom, you shall stand forth before the king and renounce Noma, and she +shall pass back to the care of my household. You yourself shall bring +her to where I stand, and as I take her from you I will put into your +hand a certain powder. Then you shall return to the side of the +king, and after our fashion shall give him to drink the bowl of the +first-fruits; but as you stir the beer, you will let fall into it that +powder which I have given you. The king will drink, and what he leaves +undrunk you will throw out upon the dust. + +“Now he will rise to give out to the people his royal decree, whereby, +Prince, you are to be deposed from your place as heir, and your brother, +Nodwengo, is to be set in your seat. But of that decree never a word +shall pass his lips; if it does, recall your saying and take back the +lady Noma from where she stands beside me. I tell you that never a word +will pass his lips; for even as he rises a stroke shall take him, such +a stroke as often falls upon the fat and aged, and he will sink to the +ground snoring through his nostrils. For a while thereafter--it may be +six hours, it may be twelve--he shall lie insensible, and then a cry +will arise that the king is dead!” + +“Ay,” said Hafela, “and that I have poisoned him!” + +“Why, Prince? Few know what is in your father’s mind, and with those, +being king, you will be able to deal. Also this is the virtue of the +poison which I choose, that it is swift, yet the symptoms of it are the +symptoms of a natural sickness. But that your safety and mine may be +assured, I have made yet another plan, though of this there will be +little need. You were present two days since when a runner came from the +white man who sojourns beyond our border, he who seeks to teach us, the +Children of Fire, a new faith, and gives out that he is the messenger of +the King of heaven. This runner asked leave for the white man to visit +the Great Place, and, speaking in the king’s name, I gave him leave. But +I warned his servant that if his master came, a sign should be required +of him to show that he was a true man, and had of the wisdom of the King +of Heaven; and that if he failed therein, then that he should die as +that white liar died who visited us in bygone years. + +“Now I have so ordered that this white man, passing through the Valley +of Death yonder, shall reach the Great Place not long before the king +drinks of the cup of the first-fruits. Then if any think that something +out of nature has happened to the king, they will surely think also that +this strange prayer-doctor has wrought the evil. Then also I will call +for a sign from the white man, praying of him to recover the king of his +sickness; and when he fails, he shall be slain as a worker of spells and +the false prophet of a false god, and so we shall be rid of him and +his new faith, and you shall be cleared of doubt. Is not the plan good, +Prince?” + +“It is very good, Hokosa--save for one thing only.” + +“For what thing?” + +“This: the white man who is named Messenger might chance to be a true +prophet of a true God, and to recover the king.” + +“Oho, let him do it, if he can; but to do it, first he must know the +poison and its antidote. There is but one, and it is known to me only +of all men in this land. When he has done that, then I, yes, even I, +Hokosa, will begin to inquire concerning this God of his, who shows +Himself so mighty in person of His messenger.” And he laughed low and +scornfully. + +“Prince, farewell! I go forth alone, whither you dare not follow at this +hour, to seek that which we shall need. One word--think not to play +me false, or to cheat me of my price; for whate’er betides, be sure of +this, that hour shall be the hour of your dooming. Hail to you, Son of +the King! Hail! and farewell.” Then, removing the door-board, the wizard +passed from the hut and was gone. + +***** + +The vision changed. Now there appeared a valley walled in on either side +with sloping cliffs of granite; a desolate place, sandy and, save for a +single spring, without water, strewn with boulders of rock, some of them +piled fantastically one upon the other. At a certain spot this valley +widened out, and in the mouth of the space thus formed, midway between +the curved lines of the receding cliffs, stood a little hill or koppie, +also built up of boulders. It was a place of death; for all around the +hill, and piled in hundreds between the crevices of its stones, lay the +white bones of men. + +Nor was this all. Its summit was flat, and in the midst of it stood +a huge tree. Even had it not been for the fruit which hung from its +branches, the aspect of that tree must have struck the beholder as +uncanny, even as horrible. The bark on its great bole was leprous white; +and from its gaunt and spreading rungs rose branches that subdivided +themselves again and again, till at last they terminated in round green +fingers, springing from grey, flat slabs of bark, in shape not unlike +that of a human palm. Indeed, from a little distance this tree, +especially if viewed by moonlight, had the appearance of bearing on +it hundreds or thousands of the arms and hands of men, all of them +stretched imploringly to Heaven. + +Well might they seem to do so, seeing that to its naked limbs hung the +bodies of at least twenty human beings who had suffered death by order +of the king or his captains, or by the decree of the company of wizards, +whereof Hokosa was the chief. There on the Hill of Death stood the Tree +of Death; and that in its dank shade, or piled upon the ground beneath +it, hung and lay the pitiful remnants of the multitudes who for +generations had been led thither to their doom. + +Now, in Owen’s vision a man was seen approaching by the little pathway +that ran up the side of the mount--the Road of Lost Footsteps it was +called. It was Hokosa the wizard. Outside the circle of the tree he +halted, and drawing a tanned skin from a bundle of medicines which he +carried, he tied it about his mouth; for the very smell of that tree is +poisonous and must not be suffered to reach the lungs. + +Presently he was under the branches, where once again he halted; this +time it was to gaze at the body of an old man which swung to and fro in +the night breeze. + +“Ah! friend,” he muttered, “we strove for many years, but it seems that +I have conquered at the last. Well, it is just; for if you could have +had your way, your end would have been my end.” + +Then very leisurely, as one who is sure that he will not be interrupted, +Hokosa began to climb the tree, till at length some of the green fingers +were within his reach. Resting his back against a bough, one by one he +broke off several of them, and averting his face so that the fumes of +it might not reach him, he caused the thick milk-white juice that they +contained to trickle into the mouth of a little gourd which was hung +about his neck by a string. When he had collected enough of the poison +and carefully corked the gourd with a plug of wood, he descended the +tree again. At the great fork where the main branches sprang from the +trunk, he stood a while contemplating a creeping plant which ran up +them. It was a plant of naked stem, like the tree it grew upon; and, +also like the tree, its leaves consisted of bunches of green spikes +having a milky juice. + +“Strange,” he said aloud, “that Nature should set the bane and the +antidote side by side, the one twined about the other. Well, so it is in +everything; yes, even in the heart of man. Shall I gather some of this +juice also? No; for then I might repent and save him, remembering that +he has loved me, and thus lose her I seek, her whom I must win back or +be withered. Let the messenger of the King of Heaven save him, if he +can. This tree lies on his path; perchance he may prevail upon its dead +to tell him of the bane and of the antidote.” And once more the wizard +laughed mockingly. + +***** + +The vision passed. At this moment Thomas Owen, recovering from his +swoon, lifted his head from the window-place. The night before him was +as black as it had been, and behind him the little American clock +was still striking the hour of midnight. Therefore he could not have +remained insensible for longer than a few seconds. + +A few seconds, yet how much he had seen in them. Truly his want of +faith had been reproved--truly he also had been “warned of God in a +dream,”--truly “his ears had been opened and his instruction sealed.” + His soul had been “kept back from the pit,” and his life from “perishing +by the sword;” and the way of the wicked had been made clear to him “in +a dream, in a vision of the night when deep sleep falleth upon men.” + +Not for nothing had he endured that agony, and not for nothing had he +struggled in the grip of doubt. + + + +CHAPTER V + +THE FEAST OF THE FIRST-FRUITS + +On the third morning from this night whereof the strange events have +been described, an ox-waggon might have been seen outspanned on the +hither side of those ranges of hills that were visible from the river. +These mountains, which although not high are very steep, form the outer +barrier and defence of the kingdom of the Amasuka. Within five hundred +yards of where the waggon stood, however, a sheer cliffed gorge, +fire-riven and water-hewn, pierced the range, and looking on it, Owen +knew it for the gorge of his dream. Night and day the mouth of it was +guarded by a company of armed soldiers, whose huts were built high on +outlook places in the mountains, whence their keen eyes could scan the +vast expanses of plain. A full day before it reached them, they had seen +the white-capped waggon crawling across the veldt, and swift runners had +reported its advent to the king at his Great Place. + +Back came the word of the king that the white man, with the waggon and +his servant, were to be led on towards the Great Place at such speed as +would bring him there in time for him to behold the last ceremony of the +feast of first-fruits; but, for the present, that the waggon itself +and the oxen were to be left at the mouth of the gorge, in charge of a +guard, who would be answerable for them. + +Now, on this morning the captain of the guard and his orderlies advanced +to the waggon and stood in front of it. They were splendid men, armed +with great spears and shields, and adorned with feather head-dresses and +all the wild finery of their regiment. Owen descended from the waggon +and came to meet them, and so for a few moments they remained, face to +face, in silence. A strange contrast they presented as they stood there; +the bare-headed white man frail, delicate, spiritual of countenance, and +the warriors great, grave, powerful, a very embodiment of the essence +of untamed humanity, an incarnate presentation of the spirit of savage +warfare. + +“How are you named, White Man?” asked the captain. + +“Chief, I am named Messenger.” + +“The peace of the king be with you, Messenger,” said the captain, +lifting his spear. + +“The peace of God be with you, Chief,” answered Owen, holding up his +hands in blessing. + +“Who is God?” asked the captain. + +“Chief, He is the King I serve, and His word is between my lips.” + +“Then pass on, Messenger of God, and deliver the word of God your King +into the ears of my king, at his Great Place yonder. Pass on riding the +beast you have brought with you, for the way is rough; but your waggon, +your oxen, and your servants, save this man only who is of the Children +of Fire, must stay here in my keeping. Fear not, Messenger, I will hold +them safe.” + +“I do not fear, Chief, there is honour in your eyes.” + +***** + +Some hours later, Owen, mounted on his mule, was riding through the +gorge, a guard in front of and behind him, and with them carriers who +had been sent to bear his baggage. At his side walked his disciple John, +and his face was sad. + +“Why are you still afraid?” asked Owen. + +“Ah! father, because this is a place of fear. Here in this valley men +are led to die; presently you will see.” + +“I have seen,” answered Owen. “Yonder where we shall halt is a mount, +and on that mount stands a tree; it is called the Tree of Death, and it +stretches a thousand hands to Heaven, praying for mercy that does not +come, and from its boughs there hangs fruit, a fruit of dead men--yes, +twenty of them hang there this day.” + +“How know you these things, my father,” asked the man amazed, “seeing +that I have never spoken to you of them?” + +“Nay,” he answered, “God has spoken to me. My God and your God.” + +Another hour passed, and they were resting by the spring of water, near +to the shadow of the dreadful tree, for in that gorge the sun burned +fiercely. John counted the bodies that swung upon it, and again looked +fearfully at Owen, for there were twenty of them. + +“I desire to go up to that tree,” Owen said to the guard. + +“As you will, Messenger,” answered their leader; “I have no orders to +prevent you from so doing. Still,” he added with a solemn smile, “it is +a place that few seek of their own will, and, because I like you well, +Messenger, I pray it may never be my duty to lead you there of the +king’s will.” + +Then Owen went up to the tree and John with him, only John would not +pass beneath the shadow of its branches; but stood by wondering, while +his master bound a handkerchief about his mouth. + +“How did he know that the breath of the tree is poisonous?” John +wondered. + +Owen walked to the bole of the tree, and breaking off some of the +finger-like leaves of the creeper that twined about it, he pressed +their milky juice into a little bottle that he had made ready. Then he +returned quickly, for the sights and odours of the place were not to be +borne. + +Outside the circle of the branches he halted, and removed the +handkerchief from his mouth. + +“Be of good cheer,” he said to John, “and if it should chance that I am +called away before my words come true, yet remember my words. I tell +you that this Tree of Death shall become the Tree of Life for all the +children of your people. Look! there above you is its sign and promise.” + +John lifted his eyes, following the line of Owen’s outstretched hand, +and saw this. High up upon the tree, and standing clear of all the other +branches, was one straight, dead limb, and from this dead limb two +arms projected at right angles, also dead and snapped off short. Had a +carpenter fashioned a cross of wood and set it there, its proportions +could not have been more proper and exact. It was very strange to find +this symbol of the Christian hope towering above that place of human +terror, and stranger still was the purpose which it must serve in a day +to come. + +Owen and John returned to the guard in silence, and presently they set +forward on their journey. At length, passing beneath a natural arch of +rock, they were out of the Valley of Death, and before them, not five +hundred paces away, appeared the fence of the Great Place. + +This Great Place stood upon a high plateau, in the lap of the +surrounding hills, all of which were strongly fortified with schanses, +pitfalls, and rough walls of stone. That plateau may have measured +fifteen miles in circumference, and the fence of the town itself was +about four miles in circumference. Within the fence and following its +curve, for it was round, stood thousands of dome-shaped huts carefully +set out in streets. Within these again was a stout stockade of timber, +enclosing a vast arena of trodden earth, large enough to contain all +the cattle of the People of Fire in times of danger, and to serve as a +review ground for their _impis_ in times of peace or festival. + +At the outer gate of the kraal there was a halt, while the keepers of +the gate despatched a messenger to their king to announce the advent of +the white man. Of this pause Owen took advantage to array himself in the +surplice and hood which he had brought with him in readiness for that +hour. Then he gave the mule to John to lead behind him. + +“What do you, Messenger?” asked the leader of the guard, astonished. + +“I clothe myself in my war-dress,” he answered. + +“Where then is your spear, Messenger?” + +“Here,” said Owen, presenting to his eyes a crucifix of ivory, most +beautifully carved. + +“I perceive that you are of the family of wizards,” said the man, and +fell back. + +Now they entered the kraal and passed for three hundred yards or more +through rows of huts, till they reached the gate of the stockade, which +was opened to them. Once within it, Owen saw a wonderful sight, such a +sight as few white men have seen. The ground of the enormous oval before +him was not flat. Either from natural accident or by design it sloped +gently upwards, so that the spectator, standing by the gate or at +the head of it before the house of the king, could take in its whole +expanse, and, if his sight were keen enough, could see every individual +gathered there. + +On the particular day of Owen’s arrival it was crowded with regiments, +twelve of them, all dressed in their different uniforms and bearing +shields to match, not one of which was less than 2500 strong. At this +moment the regiments were massed in deep lines, each battalion by +itself, on either side of the broad roadway that ran straight up the +kraal to where the king, his sons, his advisers and guards, together +with the company of wizards, were placed in front of the royal house. + +There they stood in absolute silence, like tens of thousands of bronze +statues, and Owen perceived that either they were resting or that they +were gathered thus to receive him. That the latter was the case soon +became evident, for as he appeared, a white spot at the foot of the +slope, countless heads turned and myriads of eyes fastened themselves +upon him. For an instant he was dismayed; there was something terrifying +in this numberless multitude of warriors, and the thought of the task +that he had undertaken crushed his spirit. Then he remembered, and +shaking off his fear and doubt, alone, save for his disciple John, +holding the crucifix aloft, he walked slowly up the wide road towards +the place where he guessed that the king must be. His arm was weary ere +ever he reached it, but at length he found himself standing before a +thickset old man, who was clad in leopard skins and seated upon a stool +of polished wood. + +“It is the king,” whispered John behind him. + +“Peace be to you,” said Owen, breaking the silence. + +“The wish is good, may it be fulfilled,” answered the king in a deep +voice, sighing as he said the words. “Yet yours is a strange greeting,” + he added. “Whence came you, White Man, how are you named, and what is +your mission to me and to my people?” + +“King, I come from beyond the sea; I am named Messenger, and my mission +is to deliver to you the saying of God, my King and--yours.” + +At these words a gasp of astonishment went up from those who stood +within hearing, expecting as they did to see them rewarded by instant +death. But Umsuka only said:-- + +“‘My King and yours’? Bold words, Messenger. Where then is this King to +whom I, Umsuka, should bow the knee?” + +“He is everywhere--in the heavens, on the earth, and below the earth.” + +“If He is everywhere, then He is here. Show me the likeness of this +King, Messenger.” + +“Behold it,” Owen answered, thrusting forward the crucifix. + +Now all the great ones about the king stared at this figure of a dying +man crowned with thorns and hanging on a cross, and then drew up their +lips to laugh. But that laugh never left them; a sudden impulse, a +mysterious wave of feeling choked it in their throats. A sense of the +strangeness of the contrast between themselves in their armed multitudes +and this one white-robed man in his loneliness took hold of them, and +with it another sense of something not far removed from fear. + +“A wizard indeed,” they thought in their hearts, and what they thought +the king uttered. + +“I perceive,” he said, “that you are either mad, White Man, or you are +a prince of wizards. Mad you do not seem to be, for your eyes are calm, +therefore a wizard you must be. Well, stand behind me: by-and-by I will +hear your message and ask of you to show me your powers; but before then +there are things which I must do. Are the lads ready? Ho, you, loose the +bull!” + +At the command a line of soldiers moved from the right, forming itself +up in front of the king and his attendants, revealing a number of +youths, of from sixteen to seventeen years of age, armed with sticks +only, who stood in companies outside a massive gate. Presently this gate +was opened, and through it, with a mad bellow, rushed a wild buffalo +bull. On seeing them the brute halted, and for a few moments stood +pawing the earth and tearing it with its great horns. Then it put down +its head and charged. Instead of making way for it, uttering a shrill +whistling sound, the youths rushed at the beast, striking with their +sticks. + +Another instant, and one of them appeared above the heads of his +companions, thrown high into the air, to be followed by a second and a +third. Now the animal was through the throng and carrying a poor boy on +its horn, whence presently he fell dead; through and through the ranks +of the regiments it charged furiously backward and forward. + +Watching it fascinated, Owen noted that it was a point of honour for +no man to stir before its rush; there they stood, and if the bull gored +them, there they fell. At length, exhausted and terrified, the brute +headed back straight up the lane where the main body of the youths were +waiting for it. Now it was among them, and, reckless of wounds or death, +they swarmed about it like bees, seizing it by legs, nose, horns and +tail, till with desperate efforts they dragged it to the ground and beat +the life out of it with their sticks. This done, they formed up before +the king and saluted him. + +“How many are killed?” he asked. + +“Eight in all,” was the answer, “and fifteen gored.” + +“A good bull,” he said with a smile; “that of last year killed but five. +Well, the lads fought him bravely. Let the dead be buried, the hurt +tended, or, if their harms are hopeless, slain, and to the rest give a +double ration of beer. Ho, now, fall back, men, and make a space for the +Bees and the Wasps to fight in.” + +Some orders were given and a great ring was formed, leaving an arena +clear that may have measured a hundred and fifty yards in diameter. Then +suddenly, from opposite sides, the two regiments, known as the Bees +and the Wasps respectively, rushed upon each other, uttering their +war-cries. + +“I put ten head of cattle on the Bees; who wagers on the Wasps?” cried +the king. + +“I, Lord,” answered the Prince Hafela, stepping forward. + +“You, Prince!” said the king with a quick frown. “Well, you are right to +back them, they are your own regiment. Ah! they are at it.” + +By this time the scene was that of a hell broken loose upon the earth. +The two regiments, numbering some 5000 men in all, had come together, +and the roar of their meeting shields was like the roar of thunder. They +were armed with kerries only, and not with spears, for the fight was +supposed to be a mimic one; but these weapons they used with such effect +that soon hundreds of them were down dead or with shattered skulls and +bruised limbs. Fiercely they fought, while the whole army watched, for +their rivalry was keen and for many months they had known that they were +to be pitted one against the other on this day. Fiercely they fought, +while the captains cried their orders, and the dust rose up in clouds +as they swung to and fro, breast thrusting against breast. At length the +end came; the Bees began to give, they fell back ever more quickly till +their retreat was a rout, and, leaving many stretched upon the ground, +amid the mocking cries of the army they were driven to the fence, by +touching which they obtained peace at the hands of their victors. + +The king saw, and his somewhat heavy, quiet face grew alive with rage. + +“Search and see,” he said, “if the captain of the Bees is alive and +unhurt.” + +Messengers went to do his bidding, and presently they returned, bringing +with them a man of magnificent appearance and middle age, whose left arm +had been broken by a blow from a kerry. With his right hand he saluted +first the king, then the Prince Nodwengo, a kindly-faced, mild-eyed man, +in whose command he was. + +“What have you to say?” asked the king, in a cold voice of anger. “Know +you that you have cost me ten head of the royal white cattle?” + +“King, I have nothing to say,” answered the captain calmly, “except that +my men are cowards.” + +“That is certainly so,” said the king. “Let all the wounded among +them be carried away; and for you, captain, who turn my soldiers into +cowards, you shall die a dog’s death, hanging to-morrow on the Tree of +Doom. As for your regiment, I banish it to the fever country, there to +hunt elephants for three years, since it is not fit to fight with men.” + +“It is well,” replied the captain, “since death is better than shame. +Only King, I have done you good service in the past; I ask that it may +be presently and by the spear.” + +“So be it,” said the king. + +“I crave his life, father,” said the Prince Nodwengo; “he is my friend.” + +“A prince should not choose cowards for his friends,” replied the king; +“let him be killed, I say.” + +Then Owen, who had been watching and listening, his heart sick with +horror, stood forward and said:-- + +“King, in the name of Him I serve, I conjure you to spare this man and +those others that are hurt, who have done no crime except to be driven +back by soldiers stronger than themselves.” + +“Messenger,” answered the king, “I bear with you because you are +ignorant. Know that, according to our customs, this crime is the +greatest of crimes, for here we show no mercy to the conquered.” + +“Yet you should do so,” said Owen, “seeing that you also must ere long +be conquered by death, and then how can you expect mercy who have shown +none?” + +“Let him be killed!” said the king. + +“King!” cried Owen once more, “do this deed, and I tell you that before +the sun is down great evil will overtake you.” + +“Do you threaten me, Messenger? Well, we will see. Let him be killed, I +say.” + +Then the man was led away; but, before he went he found time to thank +Owen and Nodwengo the prince, and to call down good fortune upon them. + + + +CHAPTER VI + +THE DRINKING OF THE CUP + +Now the king’s word was done, the anger went out of his eyes, and once +more his countenance grew weary. A command was issued, and, with the +most perfect order, moving like one man, the regiments changed their +array, forming up battalion upon battalion in face of the king, that +they might give him the royal salute so soon as he had drunk the cup of +the first-fruits. + +A herald stood forward and cried:-- + +“Hearken, you Sons of Fire! Hearken, you Children of Umsuka, Shaker of +the Earth! Have any of you a boon to ask of the king?” + +Men stood forward, and having saluted, one by one asked this thing or +that. The king heard their requests, and as he nodded or turned his head +away, so they were granted or refused. + +When all had done, the Prince Hafela came forward, lifted his spear, and +cried:-- + +“A boon, King!” + +“What is it?” asked his father, eyeing him curiously. + +“A small matter, King,” he replied. “A while ago I named a certain +woman, Noma, the ward of Hokosa the wizard, and she was sealed to me +to fill the place of my first wife, the queen that is to be. She passed +into the House of the Royal Women, and, by your command, King, it was +fixed that I should marry her according to our customs to-morrow, +after the feast of the first-fruits is ended. King, my heart is changed +towards that woman; I no longer desire to take her to wife, and I pray +that you will order that she shall now be handed back to Hokosa her +guardian.” + +“You blow hot and cold with the same mouth, Hafela,” said Umsuka, “and +in love or war I do not like such men. What have you to say to this +demand, Hokosa?” + +Now Hokosa stepped forward from where he stood at the head of the +company of wizards. His dress, like that of his companions, was simple, +but in its way striking. On his shoulders he wore a cloak of shining +snakeskin; about his loins was a short kilt of the same material; and +round his forehead, arms and knees were fillets of snakeskin. At his +side hung his pouch of medicines, and in his hand he held no spear, but +a wand of ivory, whereof the top was roughly carved so as to resemble +the head of a cobra reared up to strike. + +“King,” he said, “I have heard the words of the prince, and I do not +think that this insult should have been put upon the Lady Noma, my ward, +or upon me, her guardian. Still, let it be, for I would not that +one should pass from under the shadow of my house whither she is not +welcome. Without my leave the prince named this woman as his queen, as +he had the right to do; and without my leave he unnames her, as he has +the right to do. Were the prince a common man, according to custom he +should pay a fine of cattle to be held by me in trust for her whom he +discards; but this is a matter that I leave to you, King.” + +“You do well, Hokosa,” answered Umsuka, “to leave this to me. Prince, +you would not wish the fine that you should pay to be that of any common +man. With the girl shall be handed over two hundred head of cattle. +More, I will do justice: unless she herself consents, she shall not be +put away. Let the Lady Noma be summoned.” + +Now the face of Hafela grew sullen, and watching, Owen saw a swift +change pass over that of Hokosa. Evidently he was not certain of the +woman. Presently there was a stir, and from the gates of the royal house +the Lady Noma appeared, attended by women, and stood before the king. +She was a tall and lovely girl, and the sunlight flashed upon her +bronze-hued breast and her ornaments of ivory. Her black hair was +fastened in a knot upon her neck, her features were fine and small, her +gait was delicate and sure as that of an antelope, and her eyes were +beautiful and full of pride. There she stood before the king, looking +round her like a stag. Seeing her thus, Owen understood how it came +about that she held two men so strangely different in the hollow of her +hand, for her charm was of a nature to appeal to both of them--a charm +of the spirit as well as of the flesh. And yet the face was haughty, a +face that upon occasion might even become cruel. + +“You sent for me and I am here, O King,” she said, in a slow and quiet +voice. + +“Listen, girl,” answered the king. “A while ago the Prince Hafela, my +son, named you as her who should be his queen, whereon you were taken +and placed in the House of the Royal Women, to abide the day of your +marriage, which should be to-morrow.” + +“It is true that the prince has honoured me thus, and that you have been +pleased to approve of his choice,” she said, lifting her eyebrows. “What +of it, O King?” + +“This, girl: the prince who was pleased to honour you is now pleased to +dishonour you. Here, in the presence of the council and army, he prays +of me to annul his sealing to you, and to send you back to the house of +your guardian, Hokosa the wizard.” + +Noma started, and her face grew hard. + +“Is it so?” she said. “Then it would seem that I have lost favour in the +eyes of my lord the prince, or that some fairer woman has found it.” + +“Of these matters I know nothing,” replied the king; “but this I know, +that if you seek justice you shall have it. Say but the word, and he to +whom you were promised in marriage shall take you in marriage, whether +he wills or wills it not.” + +At this speech, the face of Hafela was suddenly lit up as with the fire +of hope, while over that of Hokosa there passed another subtle change. +The girl glanced at them both and was silent for a while. Her breast +heaved and her white teeth bit upon her lip. To Owen, who noted all, it +was clear that rival passions were struggling in her heart: the passion +of power and the passion of love, or of some emotion which he did not +understand. Hokosa fixed his calm eyes upon her with a strange intensity +of gaze, and while he gazed his form quivered with a suppressed +excitement, much as a snake quivers that is about to strike its prey. +To the careless eye there was nothing remarkable about his look +and attitude; to the observer it was evident that both were full of +extraordinary purpose. He was talking to the girl, not with words, but +in some secret language that he and she understood alone. She started as +one starts who catches the tone of a well-remembered voice in a crowd of +strangers, and lifting her eyes from the ground, whither she had turned +them in meditation, she looked up at Hokosa. + +Instantly her face began to change. The haughtiness and anger went out +of it, it grew troubled, the lips parted in a sigh. First she bent her +head and body towards him, then without more ado she walked to where he +stood and took him by the hand. Here, at some whispered word or sign, +she seemed to recover herself, and again resuming the character of a +proud offended beauty, she curtseyed to Umsuka, and spoke:-- + +“O King, as you see, I have made my choice. I will not force myself upon +a man who scorns me, no, not even to share his place and power, though +it is true that I love them both. Nay, I will return to Hokosa my +guardian, and to his wife, Zinti, who has been as my mother, and with +them be at peace.” + +“It is well,” said the king, “and perhaps, girl, your choice is wise; +perhaps your loss is not so great as you have thought. Hafela, take you +the hand of Hokosa and release the girl back to him according to the +law, promising in the ears of men before the first month of winter to +pay him two hundred head of cattle as forfeit, to be held by him in +trust for the girl.” + +In a sullen voice, his lips trembling with rage, Hafela did as the +king commanded; and when the hands of the conspirators unclasped, Owen +perceived that in that of the prince lay a tiny packet. + +“Mix me the cup of the first-fruits, and swiftly,” said the king again, +“for the sun grows low in the heavens, and ere it sinks I have words to +say.” + +Now a polished gourd filled with native beer was handed to Nodwengo, the +second son of the king, and one by one the great councillors approached, +and, with appropriate words, let fall into it offerings emblematic of +fertility and increase. The first cast in a grain of corn; the second, +a blade of grass; the third, a shaving from an ox’s horn; the fourth, +a drop of water; the fifth, a woman’s hair; the sixth, a particle +of earth; and so on, until every ingredient was added to it that was +necessary to the magic brew. + +Then Hokosa, as chief of the medicine men, blessed the cup according +to the ancient forms, praying that he whose body was the heavens, whose +eyes were lightning, and whose voice was thunder, the spirit whom they +worshipped, might increase and multiply to them during the coming year +all those fruits and elements that were present in the cup, and that +every virtue which they contained might comfort the body of the king. + +His prayer finished, it was the turn of Hafela to play his part as the +eldest born of the king. Kneeling over the cup which stood upon the +ground, a spear was handed to him that had been made red hot in the +fire. Taking the spear, he stabbed with it towards the four quarters +of the horizon; then, muttering some invocation, he plunged it into the +bowl, stirring its contents till the iron grew black. Now he threw aside +the spear, and lifting the bowl in both hands, he carried it to his +father and offered it to him. + +Although he had been unable to see him drop the poison into the cup, +a glance at Hafela told Owen that it was there; for though he kept his +face under control, he could not prevent his hands from twitching or the +sweat from starting upon his brow and breast. + +The king rose, and taking the bowl, held it on high, saying:-- + +“In this cup, which I drink on behalf of the nation, I pledge you, my +people.” + +It was the signal for the royal salute, for which each regiment had been +prepared. As the last word left the king’s lips, every one of the thirty +thousand men present in that great place began to rattle his kerry +against the surface of his ox-hide shield. At first the sound produced +resembled that of the murmur of the sea; but by slow and just degrees +it grew louder and ever louder, till the roar of it was like the deepest +voice of thunder, a sound awe-inspiring, terrible. + +Suddenly, when its volume was most, four spears were thrown into the +air, and at this signal every man ceased to beat upon his shield. In the +place itself there was silence, but from the mountains around the echoes +still crashed and volleyed. When the last of them had died away, the +king brought the cup to the level of his lips. Owen saw, and knowing its +contents, was almost moved to cry out in warning. Indeed, his arm was +lifted and his mouth was open, when by chance he noted Hokosa watching +him, and remembered. To act now would be madness, his time had not yet +come. + +The cup touched the king’s lips, and at the sign from every throat +in that countless multitude sprang the word “_King!_” and every foot +stamped upon the ground, shaking the solid earth. Thrice the monarch +drank, and thrice this tremendous salute, the salute of the whole nation +to its ruler, was repeated, each time more loudly than the last. Then +pouring the rest of the liquor on the ground, Umsuka set aside the cup, +and in the midst of a silence that seemed deep after the crash of the +great salute, he began to address the multitude:-- + +“Hearken, Councillors and Captains, and you, my people, hearken. As you +know, I have two sons, calves of the Black Bull, princes of the land--my +son Hafela, the eldest born, and my son Nodwengo, his half-brother----” + +At this point the king began to grow confused. He hesitated, passing his +hand over his eyes, then slowly and with difficulty repeated those words +which he had already said. + +“We hear you, Father,” cried the councillors in encouragement, as for +the second time he paused. While they still spoke, the veins in the +king’s neck were seen to swell suddenly, foam flecked with blood burst +from his lips, and he fell headlong to the ground. + + + +CHAPTER VII + +THE RECOVERY OF THE KING + +For a moment there was silence, then a great cry arose--a cry of “Our +father is dead!” Presently with it were mingled other and angrier shouts +of “The king is murdered!” and “He is bewitched, the white wizard +has bewitched the king! He prophesied evil upon him, and now he has +bewitched him!” + +Meanwhile the captains and councillors formed a ring about Umsuka, and +Hokosa bending over him examined him. + +“Princes and Councillors,” he said presently, “your father yet lives, +but his life is like the life of a dying fire and soon he must be dead. +This is sure, that one of two things has befallen him: either the heat +has caused the blood to boil in his veins and he is smitten with a +stroke from heaven, such as men who are fat and heavy sometimes die of; +or he has been bewitched by a wicked wizard. Yonder stands one,” and he +pointed to Owen, “who not an hour ago prophesied that before the sun was +down great evil should overtake the king. The sun is not yet down, and +great evil has overtaken him. Perchance, Princes and Councillors, this +white prophet can tell us of the matter.” + +“Perchance I can,” answered Owen calmly. + +“He admits it!” cried some. “Away with him!” + +“Peace!” said Owen, holding the crucifix towards those whose spears +threatened his life. + +They shrank back, for this symbol of a dying man terrified them who +could not guess its significance. + +“Peace,” went on Owen, “and listen. Be sure of this, Councillors, that +if I die, your king will die; whereas if I live, your king may live. You +ask me of this matter. Where shall I begin? Shall I begin with the tale +of two men seated together some nights ago in a hut so dark that no eyes +could see in it, save perchance the eyes of a wizard? What did they talk +of in that hut, and who were those men? They talked, I think, of the +death of a king and of the crowning of a king. They talked of a price +to be paid for a certain medicine; and one of them had a royal air, and +one----” + +“Will ye hearken to this wild babbler while your king lies dying before +your eyes?” broke in Hokosa, in a shrill, unnatural voice; for almost +palsied with fear as he was at Owen’s mysterious words, he still +retained his presence of mind. “Listen now: what is he, and what did he +say? He is one who comes hither to preach a new faith to us; he comes, +he says, on an embassy from the King of Heaven, who has power over +all things, and who, so these white men preach, can give power to His +servants. Well, let this one cease prating and show us his strength, +as he has been warned he would be called upon to do. Let him give us a +sign. There before you lies your king, and he is past the help of man; +even I cannot help him. Therefore, let this messenger cure him, or call +upon his God to cure him; that seeing, we may know him to be a true +messenger, and one sent by that King of whom he speaks. Let him do this +now before our eyes, or let him perish as a wizard who has bewitched the +king. Do you hear my words, Messenger, and can you draw this one back +from between the Gates of Death?” + +“I hear them,” answered Owen quietly; “and I can--or if I cannot, then +I am willing to pay the penalty with my life. You who are a doctor say +that your king is as one who is already dead, so that whatever I may do +I cannot hurt him further. Therefore I ask this of you, that you stand +round and watch, but molest me neither by word nor deed while I attempt +his cure. Do you consent?” + +“It is just; we consent,” said the councillors. “Let us see what the +white man can do, and by the issue let him be judged.” But Hokosa stared +at Owen wondering, and made no answer. + +“Bring some clean water to me in a gourd,” said Owen. + +It was brought and given to him. He looked round, searching the faces of +those about him. Presently his eye fell upon the Prince Nodwengo, and he +beckoned to him, saying:-- + +“Come hither, Prince, for you are honest, and I would have you to help +me, and no other man.” + +The prince stepped forward and Owen gave him the gourd of water. Then +he drew out the little bottle wherein he had stored the juice of the +creeper, and uncorking it, he bade Nodwengo fill it up with water. This +done, he clasped his hands, and lifting his eyes to heaven, he prayed +aloud in the language of the Amasuka. + +“O God,” he prayed, “upon whose business I am here, grant, I beseech +Thee, that by Thy Grace power may be given to me to work this miracle in +the face of these people, to the end that I may win them to cease from +their iniquities, to believe upon Thee, the only true God, and to save +their souls alive. Amen.” + +Having finished his prayer, he took the bottle and shook it; then he +commanded Nodwengo to sit upon the ground and hold his father’s head +upon his knee. Now, as all might see by many signs, the king was upon +the verge of death, for his lips were purple, his breathing was rare and +stertorous, and his heart stood well-nigh still. + +“Open his mouth and hold down the tongue,” said Owen. + +The prince obeyed, pressing down the tongue with a snuff spoon. Then +placing the neck of the bottle as far into the throat as it would reach, +Owen poured the fluid it contained into the body of the king, who made a +convulsive movement and instantly seemed to die. + +“He is dead,” said one; “away with the false prophet!” + +“It may be so, or it may not be so,” answered Owen. “Wait for the half +of an hour; then, if he shows no sign of life, do what you will with +me.” + +“It is well,” they said; “so be it.” + +Slowly the minutes slipped by, while the king lay like a corpse before +them, and outside of that silent ring the soldiers murmured as the wind. +The sun was sinking fast, and Hokosa watched it, counting the seconds. +At length he spoke:-- + +“The half of the hour that you demanded is dead, White Man, as dead +as the king; and now the time has come for you to die also,” and he +stretched out his hand to take him. + +Owen looked at his watch and replied:-- + +“There is still another minute; and you, Hokosa, who are skilled in +medicines, may know that this antidote does not work so swiftly as the +bane.” + +The shot was a random one, but it told, for Hokosa fell back and was +silent. + +The seconds passed on as the minute hand of the watch went round from +ten to twenty, from twenty to thirty, from thirty to forty. A few +more instants and the game was played. Had that dream of his been vain +imagining, and was all his faith nothing but a dream wondered Owen? +Well, if so, it would be best that he should die. But he did not believe +that it was so; he believed that the Power above him would intervene to +save--not him, indeed, but all this people. + +“Let us make an end,” said Hokosa, “the time is done.” + +“Yes,” said Owen, “the time is done--and _the king lives!_” + +Even as he spoke the pulses in the old man’s forehead were seen to +throb, and the veins in his neck to swell as they had swollen after he +had swallowed the poison; then once more they shrank to their natural +size. Umsuka stirred a hand, groaned, sat up, and spoke:-- + +“What has chanced to me?” he said. “I have descended into deep darkness, +now once again I see light.” + +No one answered, for all were staring, terrified and amazed, at the +Messenger--the white wizard to whom had been given power to bring men +back from the gate of death. At length Owen said:-- + +“This has chanced to you, King: that evil which I prophesied to you if +you refused to listen to the voice of mercy has fallen upon you. By now +you would have been dead, had it not pleased Him Whom I serve, working +through me, His messenger, to bring you back to look upon the sun. Thank +Him, therefore, and worship Him, for He alone is Master of the Earth,” + and he held the crucifix before his eyes. + +The humbled monarch lifted his hand--he who for many years had made +obeisance to none--and saluted the symbol, saying:-- + +“Messenger, I thank Him and I worship Him, though I know Him not. Say +now, how did His magic work upon me to make me sick to death and to +recover me?” + +“By the hand of man, King, and by the virtues that lie hid in Nature. +Did you not drink of a cup, and were not many things mixed in the +draught? Was it not but now in your mind to speak words that should +bring down the head of pride and evil, and lift up the head of truth and +goodness?” + +“O White Man, how know you these things?” gasped the king. + +“I know them, it is enough. Say, who was it that stirred the bowl, King, +and who gave you to drink?” + +Now Umsuka staggered to his feet, and cried aloud in a voice that was +thick with rage:-- + +“By my head and the heads of my fathers I smell the plot! My son, the +Prince Hafela, has learned my counsel, and would have slain me before I +said words that should set him beneath the feet of Nodwengo. Seize him, +captains, and let him be brought before me for judgment!” + +Men looked this way and that to carry out the command of the king, but +Hafela was gone. Already he was upon the hillside, running as a man has +rarely run before--his face set towards that fastness in the mountains +where he could find refuge among his mother’s tribesmen and the +regiments which he commanded. Of late they had been sent thither by the +king that they might be far from the Great Place when their prince was +disinherited. + +“He is fled,” said one; “I saw him go.” + +“Pursue him and bring him back, dead or alive!” thundered the king. +“A hundred head of cattle to the man who lays hand upon him before he +reaches the _impi_ of the North, for they will fight for him!” + +“Stay!” broke in Owen. “Once before this day I prayed of you, King, to +show mercy, and you refused it. Will you refuse me a second time? Leave +him his life who has lost all else.” + +“That he may rebel against me? Well, White Man, I owe you much, and for +this time your wisdom shall be my guide, though my heart speaks against +such gentleness. Hearken, councillors and people, this is my decree: +that Hafela, my son, who would have murdered me, be deposed from his +place as heir to my throne, and that Nodwengo, his brother, be set in +that place, to rule the People of Fire after me when I die.” + +“It is good, it is just!” said the council. “Let the king’s word be +done.” + +“Hearken again,” said Umsuka. “Let this white man, who is named +Messenger, be placed in the House of Guests and treated with all honour; +let oxen be given him from the royal herds and corn from the granaries, +and girls of noble blood for wives if he wills them. Hokosa, into your +hand I deliver him, and, great though you are, know this, that if but +a hair of his head is harmed, with your goods and your life you shall +answer for it, you and all your house.” + +“Let the king’s word be done,” said the councillors again. + +“Heralds,” went on Umsuka, “proclaim that the feast of the first-fruits +is ended, and my command is that every regiment should seek its +quarters, taking with it a double gift of cattle from the king, who has +been saved alive by the magic of this white man. And now, Messenger, +farewell, for my head grows weary. To-morrow I will speak with you.” + +Then the king was led away into the royal house, and save those who were +quartered in it, the regiments passed one by one through the gates of +the kraal, singing their war-songs as they went. Darkness fell upon the +Great Place, and through it parties of men might be seen dragging thence +the corpses of those who had fallen in the fight with sticks, or been +put to death thereafter by order of the king. + +“Messenger,” said Hokosa, bowing before Owen, “be pleased to follow +me.” Then he led him to a little kraal numbering five or six large and +beautifully made huts, which stood by itself, within its own fence, at +the north end of the Great Place, not far from the house of the king. +In front of the centre hut a fire was burning, and by its light women +appeared cleaning out the huts and bringing food and water. + +“Here you may rest in safety, Messenger,” said Hokosa, “seeing that +night and day a guard from the king’s own regiment will stand before +your doors.” + +“I do not need them,” answered Owen, “for none can harm me till my hour +comes. I am a stranger here and you are a great man; yet, Hokosa, which +of us is the safest this night?” + +“Your meaning?” said Hokosa sharply. + +“O man!” answered Owen, “when in a certain hour you crept up the valley +yonder, and climbing the Tree of Death gathered its poison, went I not +with you? When, before that hour, you sat in yonder hut bargaining with +the Prince Hafela--the death of a king for the price of a girl--was I +not with you? Nay, threaten me not--in your own words I say it--‘lay +down that assegai, or by my spirit your body shall be thrown to the +kites, as that of one who would murder the king’--and the king’s guest!” + +“White Man,” whispered Hokosa throwing down the spear, “how can these +things be? I was alone in the hut with the prince, I was alone beneath +the Tree of Doom, and you, as I know well, were beyond the river. Your +spies must be good, White Man.” + +“My spirit is my only spy, Hokosa. My spirit watched you, and from your +own lips he learned the secret of the bane and of the antidote. Hafela +mixed the poison as you taught him; I gave the remedy, and saved the +king alive.” + +Now the knees of Hokosa grew weak beneath him, and he leaned against the +fence of the kraal for support. + +“I have skill in the art,” he said hoarsely; “but, Messenger, your magic +is more than mine, and my life is forfeit to you. To-morrow morning, +you will tell the king all, and to-morrow night I shall hang upon the +dreadful Tree. Well, so be it; I am overmatched at my own trade, and it +is best that I should die. You have plotted well and you have conquered, +and to you belong my place and power.” + +“It was you who plotted, and not I, Hokosa. Did you not contrive that I +should reach the Great Place but a little before the poison was given to +the king, so that upon me might be laid the crime of his bewitching? Did +you not plan also that I should be called upon to cure him--a thing +you deemed impossible--and when I failed that I should be straightway +butchered?” + +“Seeing that it is useless to lie to you, I confess that it was so,” + answered Hokosa boldly. + +“It was so,” repeated Owen; “therefore, according to your law your life +is forfeit, seeing that you dug a pit to snare the innocent feet. But +I come to tell you of a new law, and that which I preach I practise. +Hokosa, I pardon you, and if you will put aside your evil-doing, I +promise you that no word of all your wickedness shall pass my lips.” + +“It has not been my fashion to take a boon at the hand of any man, save +of the king only,” said the wizard in a humble voice; “but now it seems +that I am come to this. Tell me, White Man, what is the payment that you +seek of me?” + +“None, Hokosa, except that you cease from evil and listen with an open +heart to that message which I am sworn to deliver to you and to all your +nation. Also you would do well to put away that fair woman whose price +was the murder of him that fed you.” + +“I cannot do it,” answered the wizard. “I will listen to your teaching, +but I will not rob my heart of her it craves alone. White Man, I am not +like the rest of my nation. I have not sought after women; I have but +one wife, and she is old and childless. Now, for the first time in my +days, I love this girl--ah, you know not how!--and I will take her, and +she shall be the mother of my children.” + +“Then, Hokosa, you will take her to your sorrow,” answered Owen +solemnly, “for she will learn to hate you who have robbed her of royalty +and rule, giving her wizardries and your grey hairs in place of them.” + +And thus for that night they parted. + + + +CHAPTER VIII + +THE FIRST TRIAL BY FIRE + +On the following day, while Owen sat eating his morning meal with a +thankful heart, a messenger arrived saying that the king would receive +him whenever it pleased him to come. He answered that he would be with +him before noon, for already he had learned that among natives one loses +little by delay. A great man, they think, is rich in time, and hurries +only to wait upon his superiors. + +At the appointed hour a guard came to lead him to the royal house, and +thither Owen went, followed by John bearing a Bible. Umsuka was seated +beneath a reed roof supported by poles and open on all sides; behind him +stood councillors and attendants, and by him were Nodwengo the prince, +and Hokosa, his mouth and prophet. Although the day was hot, he wore a +kaross or rug of wild catskins, and his face showed that the effects +of the poisoned draught were still upon him. At the approach of Owen he +rose with something of an effort, and, shaking him by the hand, thanked +him for his life, calling him “doctor of doctors.” + +“Tell me, Messenger,” he added, “how it was that you were able to cure +me, and who were in the plot to kill me? There must have been more than +one,” and he rolled his eyes round with angry suspicion. + +“King,” answered Owen, “if I knew anything of this matter, the Power +that wrote it on my mind has wiped it out again, or, at the least, has +forbidden me to speak of its secret. I saved you, it is enough; for the +rest, the past is the past, and I come to deal with the present and the +future.” + +“This white man keeps his word,” thought Hokosa to himself, and he +looked at him thanking him with his eyes. + +“So be it,” answered the king; “after all, it is wise not to stir a +dung-heap, for there we find little beside evil odours and the nests of +snakes. Now, what is your business with me, and why do you come from the +white man’s countries to visit me? I have heard of those countries, they +are great and far away. I have heard of the white men also--wonderful +men who have all knowledge; but I do not desire to have anything to do +with them, for whenever they meet black people they eat them up, taking +their lands and making them slaves. Once, some years ago, two of you +white people visited us here, but perhaps you know that story.” + +“I know it,” answered Owen; “one of those men you murdered, and the +other you sent back with a message which he delivered into my ears +across the waters; thousands of miles away.” + +“Nay,” answered the king, “we did not murder him; he came to us with the +story of a new God who could raise the dead and work other miracles, and +gave such powers to His servants. So a man was slain and we begged of +him to bring him back to life; and since he could not, we killed him +also because he was a liar.” + +“He was no liar,” said Owen; “since he never told you that he had power +to open the mouth of the grave. Still, Heaven is merciful, and although +you murdered him that was sent to you, his Master has chosen me to +follow in his footsteps. Me also you may murder if you will, and then +another and another; but still the messengers shall come, till at +last your ears are opened and you listen. Only, for such deeds your +punishment must be heavy.” + +“What is the message, White Man?” + +“A message of peace, of forgiveness, and of life beyond the grave, of +life everlasting. Listen, King. Yesterday you were near to death; say +now, had you stepped over the edge of it, where would you be this day?” + +Umsuka shrugged his shoulders. “With my fathers, White Man.” + +“And where are your fathers?” + +“Nay, I know not--nowhere, everywhere: the night is full of them; in the +night we hear the echo of their voices. When they are angry they haunt +the thunder-cloud, and when they are pleased they smile in the sunshine. +Sometimes also they appear in the shape of snakes, or visit us in +dreams, and then we offer them sacrifice. Yonder on the hillside is a +haunted wood; it is full of their spirits, White Man, but they cannot +talk, they only mutter, and their footfalls sound like the dropping of +heavy rain, for they are strengthless and unhappy, and in the end they +fade away.” + +“So you say,” answered Owen, “who are not altogether without +understanding, yet know little, never having been taught. Now listen to +me,” and very earnestly he preached to him and those about him of peace, +of forgiveness, and of life everlasting. + +“Why should a God die miserably upon a cross?” asked the king at length. + +“That through His sacrifice men might become as gods,” answered Owen. +“Believe in Him and He will save you.” + +“How can we do that,” asked the king again, “when already we have a god? +Can we desert one god and set up another?” + +“What god, King?” + +“I will show him to you, White Man. Let my litter be brought.” + +The litter was brought and the king entered it with labouring breath. +Passing through the north gate of the Great Place, the party ascended a +slope of the hill that lay beyond it till they reached a flat plain some +hundreds of yards in width. On this plain vegetation grew scantily, for +here the bed rock of ironstone, denuded with frequent and heavy rains, +was scarcely hidden by a thin crust of earth. On the further side of the +plain, however, and separated from it by a little stream, was a green +bank of deep soft soil, beyond which lay a gloomy valley full of great +trees, that for many generations had been the burying-place of the kings +of the Amasuka. + +“This is the house of the god,” said the king. + +“A strange house,” answered Owen, “and where is he that dwells in it?” + +“Follow me and I will show you, Messenger; but be swift, for already the +sky grows dark with coming tempest.” + +Now at the king’s command the bearers bore him across the sere plateau +towards a stone that lay almost in its centre. Presently they halted, +and, pointing to this mass, the king said:-- + +“Behold the god!” + +Owen advanced and examined the object. A glance told him that this god +of the Amasuka was a meteoric stone of unusual size. Most of such stones +are mere shapeless lumps, but this one bore a peculiar resemblance to +a seated human being holding up one arm towards the sky. So strange was +this likeness that, other reasons apart, it seemed not wonderful that +savages should regard the thing with awe and veneration. Rather would it +have been wonderful had they not done so. + +“Say now,” said Owen to the king when he had inspected the stone, “what +is the history of this dumb god of yours, and why do you worship him?” + +“Follow me across the stream and I will tell you, Messenger,” answered +the king, again glancing at the sky. “The storm gathers, and when it +breaks none are safe upon this plain except the heaven doctors such as +Hokosa and his companions who can bind the lightning.” + +So they went and when they reached the further side of the stream Umsuka +descended from his litter. + +“Messenger,” he said, “this is the story of the god as it has come down +to us. From the beginning our land has been scourged with lightning +above all other lands, and with the floods of rain that accompany the +lightning. In the old days the Great Place of the king was out yonder +among the mountains, but every year fire from heaven fell upon it, +destroying much people: and at length in a great tempest the house of +the king of that day was smitten and burned, and his wives and children +were turned to ashes. Then that king held a council of his wizards +and fire-doctors, and these having consulted the spirits of their +forefathers, retired into a place apart to fast and pray; yes, it was +in yonder valley, the burying ground of kings, that they hid themselves. +Now on the third night the God of Fire appeared to the chief of the +doctors in his sleep, and he was shaped like a burning brand and smoke +went up from him. Out of the smoke he spoke to the doctor, saying: ‘For +this reason it is that I torment your people, that they hate me and +curse at me and pay me little honour.’ + +“In his dream the doctor answered: ‘How can the people honour a god that +they do not see?’ Then the god said: ‘Rise up now in the night, all the +company of you, and go take your stand upon the banks of yonder stream, +and I will fall down in fire from heaven, and there on the plain you +shall find my image. Then let your king move his Great Place into the +valley beneath the plain, and henceforth my bolts shall spare it and +him. Only, month by month you shall make prayers and offerings to me; +moreover, the name of the people shall be changed, for it shall be +called the People of Fire.’ + +“Now the doctor rose, and having awakened his companions, he told them +of his vision. Then they all of them went down to the banks of this +stream where we now stand. And as they waited there a great tempest +burst over them, and in the midst of that tempest they saw the flaming +figure of a man descend from heaven, and when he touched the earth it +shook. The morning came and there upon the plain before them, where +there had been nothing, sat the likeness of the god as it sits to-day +and shall sit for ever. So the name of this people was changed, and the +king’s Great Place was built where it now is. + +“Since that day, Messenger, no hut has been burned and no man killed +in or about the Great Place by fire from heaven, which falls only here +where the god is, though away among the mountains and elsewhere men are +sometimes killed. But wait a while and you shall see with your eyes. +Hokosa, do you, whom the lightning will not touch, take that pole of +dead wood and set it up yonder in the crevice of the rock not far from +the figure of the god.” + +“I obey,” said Hokosa, “although I have brought no medicines with me. +Perhaps,” he added with a faint sneer, “the white man, who is so great a +wizard, will not be afraid to accompany me.” + +Now Owen saw that all those present were looking at him curiously. +It was evident they believed that he would not dare to accept the +challenge. Therefore he answered at once and without hesitation:-- + +“Certainly I will come; the pole is heavy for one man to carry, and +where Hokosa goes, there I can go also.” + +“Nay, nay, Messenger,” said the king, “the lightning knows Hokosa and +will turn from him, but you are a stranger to it and it will eat you +up.” + +“King,” answered Owen, “I do not believe that Hokosa has any power over +the lightning. It may strike him or it may strike me; but unless my God +so commands, it will strike neither of us.” + +“On your head be it, White Man,” said Hokosa, with cold anger. “Come, +aid me with the pole.” + +Then they lifted the dead tree, and between them carried it into the +middle of the plain, where they set it up in a crevice of the rock. By +this time the storm was almost over them, and watching it Owen perceived +that the lightnings struck always along the bank of the stream, +doubtless following a hidden line of the bed of ironstone. + +“It is but a very little storm,” said Hokosa contemptuously, “such as +visit us almost every afternoon at this period of the year. Ah! White +Man, I would that you could see one of our great tempests, for these are +worth beholding. This I fear, however, that you will never do, seeing it +is likely that within some few minutes you will have passed back to that +King who sent you here, with a hole in your head and a black mark down +your spine.” + +“That we shall learn presently, Hokosa,” answered Owen; “for my part, I +pray that no such fate may overtake you.” + +Now Hokosa moved himself away, muttering and pointing with his fingers, +but Owen remained standing within about thirty yards of the pole. +Suddenly there came a glare of light, and the pole was split into +fragments; but although the shock was perceptible, they remained unhurt. +Almost immediately a second flash leaped from the cloud, and Owen saw +Hokosa stagger and fall to his knees. “The man is struck,” he thought +to himself, but it was not so, for recovering his balance, the wizard +walked back to the stream. + +Owen never stirred. From boyhood courage had been one of his good +qualities, but it was a courage of the spirit rather than of the flesh. +For instance, at this very moment, so far as his body was concerned, +he was much afraid, and did not in the least enjoy standing upon an +ironstone plateau at the imminent risk of being destroyed by lightning. +But even if he had not had an end to gain, he would have scorned to give +way to his human frailties; also, now as always, his faith supported +him. As it happened the storm, which was slight, passed by, and no more +flashes fell. When it was over he walked back to where the king and his +court were standing. + +“Messenger,” said Umsuka, “you are not only a great doctor, you are also +a brave man, and such I honour. There is no one among us here, not being +a lord of the lightning, who would have dared to stand upon that place +with Hokosa while the flashes fell about him. Yet you have done it; it +was Hokosa who was driven away. You have passed the trial by fire, and +henceforth, whether we refuse your message or accept it, you are great +in this land.” + +“There is no need to praise me, King,” answered Owen. “The risk is +something; but I knew that I was protected from it, seeing that I shall +not die until my hour comes, and it is not yet. Listen now: your god +yonder is nothing but a stone such as I have often seen before, for +sometimes in great tempests they come to earth from the clouds. You are +not the first people that have worshipped such a stone, but now we know +better. Also this plain before you is full of iron, and iron draws +the lightning. That is why it never strikes your town below. The iron +attracts it more strongly than earth and huts of straw. Again, while the +pole stood I was in little danger, for the lightning strikes the highest +thing; but after the pole was shattered and Hokosa wisely went away, +then I was in some danger, only no flashes fell. I am not a magician, +King, but I know some things that you do not know, and I trust in One +whom I shall lead you to trust also.” + +“We will talk of this more hereafter,” said the king hurriedly, “for one +day, I have heard and seen enough. Also I do not believe your words, +for I have noted ever that those who are the greatest wizards of all say +continually that they have no magic power. Hokosa, you have been famous +in your day, but it seems that henceforth you who have led must follow.” + +“The battle is not yet fought, King,” answered Hokosa. “To-day I met the +lightnings without my medicines, and it was a little storm; when I +am prepared with my medicines and the tempest is great, then I will +challenge this white man to face me yonder, and then in that hour _my_ +god shall show his strength and _his_ God shall not be able to save +him.” + +“That we shall see when the time comes,” answered Owen, with a smile. + +That night as Owen sat in his hut working at the translation of St. +John, the door was opened and Hokosa entered. + +“White Man,” said the wizard, “you are too strong for me, though whence +you have your power I know not. Let us make a bargain. Show me your +magic and I will show you mine, and we will rule the land between us. +You and I are much akin--we are great; we have the spirit sight; we +know that there are things beyond the things we see and hear and feel; +whereas, for the rest, they are fools, following the flesh alone. I have +spoken.” + +“Very gladly will I show you my magic, Hokosa,” answered Owen +cheerfully, “since, to speak truth, though I know you to be wicked, and +guess that you would be glad to be rid of me by fair means or foul; yet +I have taken a liking for you, seeing in you one who from a sinner may +grow into a saint. + +“This then is my magic: To love God and serve man; to eschew wizardry, +wealth, and power; to seek after holiness, poverty and humility; to +deny your flesh, and to make yourself small in the sight of men, that so +perchance you may grow great in the sight of Heaven and save your soul +alive.” + +“I have no stomach for that lesson,” said Hokosa. + +“Yet you shall live to hunger for it,” answered Owen. And the wizard +went away angered but wondering. + + + +CHAPTER IX + +THE CRISIS + +Now, day by day for something over a month Owen preached the Gospel +before the king, his councillors, and hundreds of the head men of the +nation. They listened to him attentively, debating the new doctrine +point by point; for although they might be savages, these people were +very keen-witted and subtle. Very patiently did Owen sow, and at length +to his infinite joy he also gathered in his first-fruit. One night as he +sat in his hut labouring as usual at the work of translation, wherein +he was assisted by John whom he had taught to read and write, the Prince +Nodwengo entered and greeted him. For a while he sat silent watching the +white man at his task, then he said:-- + +“Messenger, I have a boon to ask of you. Can you teach me to understand +those signs which you set upon the paper, and to make them also as does +John your servant?” + +“Certainly,” answered Owen; “if you will come to me at noon to-morrow, +we will begin.” + +The prince thanked him, but he did not go away. Indeed, from his manner +Owen guessed that he had something more upon his mind. At length it came +out. + +“Messenger,” he said, “you have told us of baptism whereby we are +admitted into the army of your King; say, have you the power of this +rite?” + +“I have.” + +“And is your servant here baptised?” + +“He is.” + +“Then if he who is a common man can be baptised, why may not I who am a +prince?” + +“In baptism,” answered Owen, “there is no distinction between the +highest and the lowest; but if you believe, then the door is open and +through it you can join the company of Heaven.” + +“Messenger, I do believe,” answered the prince humbly. + +Then Owen was very joyful, and that same night, with John for a witness, +he baptised the prince, giving him the new name of Constantine, after +the first Christian emperor. + +On the following day Nodwengo, in the presence of Owen, who on this +point would suffer no concealment, announced to the king that he had +become a Christian. Umsuka heard, and for a while sat silent. Then he +said in a troubled voice:-- + +“Truly, Messenger, in the words of that Book from which you read to us, +I fear that you have come hither to bring, ‘not peace but a sword.’ Now +when the witch-doctors and the priests of fire learn this, that he whom +I have chosen to succeed me has become the servant of another faith, +they will stir up the soldiers and there will be civil war. I pray you, +therefore, keep the matter secret, at any rate for a while, seeing that +the lives of many are at stake.” + +“In this, my father,” answered the prince, “I must do as the Messenger +bids me; but if you desire it, take from me the right of succession and +call back my brother from the northern mountains.” + +“That by poison or the spear he may put all of us to death, Nodwengo! +Be not afraid; ere long when he learns all that is happening here, your +brother Hafela will come from the northern mountains, and the spears of +his _impis_ shall be countless as the stars of the sky. Messenger, you +desire to draw us to the arms of your God--and myself, I am at +times minded to follow the path of my son Nodwengo and seek a refuge +there--but say, will they be strong enough to protect us from Hafela and +the warriors of the north? Already he gathers his clans, and already +my captains desert to him. By-and-by, in the spring-time--may I be dead +before the day--he will roll down upon us like a flood of water----” + +“To fall back like waters from a wall of rock,” answered Owen. “‘Let not +your heart be troubled,’ for my Master can protect His servants, and He +will protect you. But first you must confess Him openly, as your son has +done.” + +“Nay, I am too old to hurry,” said the king with a sigh. “Your tale +seems full of promise to one who is near the grave; but how can I know +that it is more than a dream? And shall I abandon the worship of my +fathers and change, or strive to change, the customs of my people to +follow after dreams? Nodwengo has chosen his part, and I do not blame +him; yet, for the present I beseech you both to keep silence on this +matter, lest to save bloodshed I should be driven to side against you.” + +“So be it, King,” said Owen; “but I warn you that Truth has a loud +voice, and that it is hard to hide the shining of a light in a dark +place, nor does it please my Lord to be denied by those who confess +Him.” + +“I am weary,” replied the old king, and they saluted him and went. + +In obedience to the wish of Umsuka his father, the conversion of +Nodwengo was kept secret, and yet--none knew how--the thing leaked out. +Soon the women in their huts, and the soldiers by their watch-fires, +whispered it in each other’s ears that he who was appointed to be +their future ruler had become a servant of the unknown God. That he had +forsworn war and all the delights of men; that he would take but one +wife and appear before the army, not in the uniform of a general, but +clad in a white robe, and carry, not the broad spear, but a cross of +wood. Swiftly the strange story flew from mouth to mouth, yet it was not +altogether believed till it chanced that one day when he was reviewing a +regiment, a soldier who was drunk with beer openly insulted the prince, +calling him “a coward who worshipped a coward.” + +Now men held their breaths, waiting to see this fool led away to die by +torture of the ant-heap or some other dreadful doom. But the prince only +answered: + +“Soldier, you are drunk, therefore I forgive you your words. Whether He +Whom you blaspheme will forgive you, I know not. Get you gone!” + +The warriors stared and murmured, for by those words, wittingly or +unwittingly, their general had confessed his faith, and that day they +made ribald songs about him in the camp. But on the morrow when they +learned how that the man whom the prince spared had been seized by +a lion and taken away as he sat at night with his companions in the +bivouac, his mouth full of boasting of his own courage in offering +insult to the prince and the new faith, then they looked at each other +askance and said little more of the matter. Doubtless it was chance, and +yet this Spirit Whom the Messenger preached was one of Whom it seemed +wisest not to speak lightly. + +But still the trouble grew, for by now the witch-doctors, with Hokosa +at the head of them, were frightened for their place and power, and +fomented it both openly and in secret. Of the women they asked what +would become of them when men were allowed to take but one wife? Of the +heads of kraals, how they would grow wealthy when their daughters ceased +to be worth cattle? Of the councillors and generals, how the land could +be protected from its foes when they were commanded to lay down the +spear? Of the soldiers, whose only trade was war, how it would please +them to till the fields like girls? Dismay took hold of the nation, and +although they were much loved, there was open talk of killing or driving +away the king and Nodwengo who favoured the white man, and of setting up +Hafela in their place. + +At length the crisis came, and in this fashion. The Amasuka, like many +other African tribes, had a strange veneration for certain varieties +of snakes which they declared to be possessed by the spirits of their +ancestors. It was a law among them that if one of these snakes entered +a kraal it must not be killed, or even driven away, under pain of death, +but must be allowed to share with the human occupants any hut that +it might select. As a result of this enforced hospitality deaths from +snake-bite were numerous among the people; but when they happened in +a kraal its owners met with little sympathy, for the doctors explained +that the real cause of them was the anger of some ancestral spirit +towards his descendants. Now, before John was despatched to instruct +Owen in the language of the Amasuka a certain girl was sealed to him +as his future wife, and this girl, who during his absence had been +orphaned, he had married recently with the approval of Owen, who at +this time was preparing her for baptism. On the third morning after his +marriage John appeared before his master in the last extremity of grief +and terror. + +“Help me, Messenger!” he cried, “for my ancestral spirit has entered our +hut and bitten my wife as she lay asleep.” + +“Are you mad?” asked Owen. “What is an ancestral spirit, and how can it +have bitten your wife?” + +“A snake,” gasped John, “a green snake of the worst sort.” + +Then Owen remembered the superstition, and snatching blue-stone and +spirits of wine from his medicine chest, he rushed to John’s hut. As it +happened, he was fortunately in time with his remedies and succeeded +in saving the woman’s life, whereby his reputation as a doctor and a +magician, already great, was considerably enlarged. + +“Where is the snake?” he asked when at length she was out of danger. + +“Yonder, under the kaross,” answered John, pointing to a skin rug which +lay in the corner. + +“Have you killed it?” + +“No, Messenger,” answered the man, “I dare not. Alas! we must live with +the thing here in the hut till it chooses to go away.” + +“Truly,” said Owen, “I am ashamed to think that you who are a Christian +should still believe so horrible a superstition. Does your faith teach +you that the souls of men enter into snakes?” + +Now John hung his head; then snatching a kerry, he threw aside the +kaross, revealing a great green serpent seven or eight feet long. With +fury he fell upon the reptile, killed it by repeated blows, and hurled +it into the courtyard outside the house. + +“Behold, father,” he said, “and judge whether I am still superstitious.” + Then his countenance fell and he added: “Yet my life must pay for this +deed, for it is an ancient law among us that to harm one of these snakes +is death.” + +“Have no fear,” said Owen, “a way will be found out of this trouble.” + +That afternoon Owen heard a great hubbub outside his kraal, and going to +see what was the matter, he found a party of the witch-doctors dragging +John towards the place of judgment, which was by the king’s house. +Thither he followed to discover that the case was already in course of +being opened before the king, his council, and a vast audience of +the people. Hokosa was the accuser. In brief and pregnant sentences, +producing the dead snake in proof of his argument, he pointed out the +enormity of the offence against the laws of the Amasuka wherewith the +prisoner was charged, demanding that the man who had killed the house of +his ancestral spirit should instantly be put to death. + +“What have you to say?” asked the king of John. + +“This, O King,” replied John, “that I am a Christian, and to me that +snake is nothing but a noxious reptile. It bit my wife, and had it not +been for the medicine of the Messenger, she would have perished of the +poison. Therefore I killed it before it could harm others.” + +“It is a fair answer,” said the king. “Hokosa, I think that this man +should go free.” + +“The king’s will is the law,” replied Hokosa bitterly; “but if the law +were the king’s will, the decision would be otherwise. This man has +slain, not a snake, but that which held the spirit of an ancestor, and +for the deed he deserves to die. Hearken, O King, for the business is +larger than it seems. How are we to be governed henceforth? Are we to +follow our ancient rules and customs, or must we submit ourselves to a +new rule and a new custom? I tell you, O King, that the people murmur; +they are without light, they wander in the darkness, they cannot +understand. Play with us no more, but let us hear the truth that we may +judge of this matter.” + +Umsuka looked at Owen, but made no reply. + +“I will answer you, Hokosa,” said Owen, “for I am the spring of all this +trouble, and at my command that man, my disciple, killed yonder snake. +What is it? It is nothing but a reptile; no human spirit ever dwelt +within it as you imagine in your superstition. You ask to hear the +truth; day by day I have preached it in your ears and you have not +listened, though many among you have listened and understood. What is it +that you seek?” + +“We seek, Messenger, to be rid of you, your fantasies and your religion; +and we demand that our king should expel you and restore the ancient +laws, or failing this, that you should prove your power openly before us +all. Your word, O King!” + +Umsuka thought for a while and answered:-- + +“This is my word, Hokosa: I will not drive the Messenger from the land, +for he is a good man; he saved my life, and there is virtue in his +teaching, towards which I myself incline. Yet it is just that he should +be asked to prove his power, so that an end may be put to doubt and all +of us may learn what god we are to worship.” + +“How can I prove my power,” asked Owen, “further than I have proved it +already? Does Hokosa desire to set up his god against my God--the false +against the true?” + +“I do,” answered the wizard with passion, “and according to the issue +let the judgment be. Let us halt no longer between two opinions, let us +become wholly Christian or rest wholly heathen, for to be divided is to +be destroyed. The magic of the Messenger is great; once and for all let +us learn if it is more than our magic. Let us put him and his doctrines +to the trial by fire.” + +“What is the trial by fire?” asked Owen. + +“You have seen something of it, White Man, but not much. This is the +trial by fire: to stand yonder before the face of the god of thunder +when a great tempest rages--not such a storm as you saw, but a storm +that splits the heavens--and to come thence unscathed. Listen: I who +am a ‘heaven-herd,’ I who know the signs of the weather, tell you that +within two days such a tempest as this will break upon us. Then White +Man, I and my companions will be ready to meet you on the plain. Take +the cross by which you swear and set it up yonder and stand by it, and +with you your converts, Nodwengo the prince, and this man whom you have +named John, if they dare to go. Over against you, around the symbol of +the god by which we swear, will stand I and my company, and we will pray +our god and you shall pray your God. Then the storm will break upon us, +and when it is ended we shall learn which of us remain alive. If you and +your cross are shattered, to us will be the victory; if we are laid low, +take it for your own. Your judgment, King!” + +Again Umsuka thought and answered:-- + +“So be it. Messenger, hear me. There is no need for you to accept this +challenge; but if you will not accept it, then go from my country in +peace, taking with you those who cleave to you. If on the other hand +you do accept it, these shall be the stakes: that if you pass the trial +unharmed, and the fire-doctors are swept away, your creed shall be my +creed and the creed of the land; but if the fire-doctors prevail against +you, then it shall be death or banishment to any who profess that creed. +Now choose!” + +“I have chosen,” said Owen. “I will meet Hokosa and his company on the +Place of fire whenever he may appoint, but for the others I cannot say.” + +“We will come with you,” said Nodwengo and John, with one voice; “where +you go, Messenger, we will surely follow.” + + + +CHAPTER X + +THE SECOND TRIAL BY FIRE + +When this momentous discussion was finished, as usual Owen preached +before the king, expounding the Scriptures and taking for his subject +the duty of faith. As he went back to his hut he saw that the snake +which John had killed had been set upon a pole in that part of the +Great Place which served as a market, and that hundreds of natives were +gathered beneath it gesticulating and talking excitedly. + +“See the work of Hokosa,” he thought to himself. “Moses set up a serpent +to save the people; yonder wizard sets up one to destroy them.” + +That evening Owen had no heart for his labours, for his mind was heavy +at the prospect of the trial which lay before him. Not that he cared for +his own life, for of this he scarcely thought; it was the prospects of +his cause which troubled him. It seemed much to expect that Heaven again +should throw over him the mantle of its especial protection, and yet +if it did not do so there was an end of his mission among the People of +Fire. Well, he did not seek this trial--he would have avoided it if +he could, but it had been thrust upon him, and he was forced to choose +between it and the abandonment of the work which he had undertaken with +such high hopes and pushed so far toward success. He did not choose the +path, it had been pointed out to him to walk upon; and if it ended in a +precipice, at least he would have done his best. + +As he thought thus John entered the hut, panting. + +“What is the matter?” Owen asked. + +“Father, the people saw and pursued me because of the death of that +accursed snake. Had I not run fast and escaped them, I think they would +have killed me.” + +“At least you have escaped, John; so be comforted and return thanks.” + +“Father,” said the man presently, “I know that you are great, and can do +many wonderful things, but have you in truth power over lightning?” + +“Why do you ask?” + +“Because a great tempest is brewing, and if you have not we shall +certainly be killed when we stand yonder on the Place of Fire.” + +“John,” he said, “I cannot speak to the lightning in a voice which it +can hear. I cannot say to it ‘go yonder,’ or ‘come hither,’ but He Who +made it can do so. Why do you tempt me with your doubts? Have I not +told you the story of Elijah the prophet and the priests of Baal? Did +Elijah’s Master forsake him, and shall He forsake us? Also this is +certain, that all the medicine of Hokosa and his wizards will not turn a +lightning flash by the breadth of a single hair. God alone can turn it, +and for the sake of His cause among these people I believe that He will +do so.” + +Thus Owen spoke on till, in reproving the weakness of another, he felt +his own faith come back to him and, remembering the past and how he +had been preserved in it, the doubt and trouble went out of his mind to +return no more. + +The third day--the day of trial--came. For sixty hours or more the +heat of the weather had been intense; indeed, during all that time the +thermometer in Owen’s hut, notwithstanding the protection of a thick +hatch, had shown the temperature to vary between a maximum of 113 and a +minimum of 101 degrees. Now, in the early morning, it stood at 108. + +“Will the storm break to-day?” asked Owen of Nodwengo, who came to visit +him. + +“They say so, Messenger, and I think it by the feel of the air. If so, +it will be a very great storm, for the heaven is full of fire. Already +Hokosa and the doctors are at their rites upon the plain yonder, but +there will be no need to join them till two hours after midday.” + +“Is the cross ready?” asked Owen. + +“Yes, and set up. It is a heavy cross; six men could scarcely carry it. +Oh! Messenger, I am not afraid--and yet, have you no medicine? If not, +I fear that the lightning will fall upon the cross as it fell upon the +pole and then----” + +“Listen, Nodwengo,” said Owen, “I know a medicine, but I will not use +it. You see that waggon chain? Were one end of it buried in the ground +and the other with a spear blade made fast to it hung to the top of the +cross, we could live out the fiercest storm in safety. But I say that +I will not use it. Are we witch doctors that we should take refuge in +tricks? No, let faith be our shield, and if it fail us, then let us die. +Pray now with me that it may not fail us.” + +***** + +It was afternoon. All round the Field of Fire were gathered thousands +upon thousands of the people of the Amasuka. The news of this duel +between the God of the white man and their god had travelled far and +wide, and even the very aged who could scarcely crawl and the little +ones who must be carried were collected there to see the issue. Nor had +they need to fear disappointment, for already the sky was half hidden +by dense thunder-clouds piled ridge on ridge, and the hush of the coming +tempest lay upon the earth. Round about the meteor stone which they +called a god, each of them stirring a little gourd of medicine that was +placed upon the ground before him, but uttering no word, were gathered +Hokosa and his followers to the number of twenty. They were all of them +arrayed in their snakeskin dresses and other wizard finery. Also each +man held in his hand a wand fashioned from a human thigh-bone. In front +of the stone burned a little fire, which now and again Hokosa fed with +aromatic leaves, at the same time pouring medicine from his bowl upon +the holy stone. Opposite the symbol of the god, but at a good distance +from it, a great cross of white wood was set up in the rock by a spot +which the witch-doctors themselves had chosen. Upon the banks of the +stream, in the place apart, were the king, his councillors and the +regiment on guard, and with them Owen, the Prince Nodwengo and John. + +“The storm will be fierce,” said the king uneasily, glancing at the +western sky, upon whose bosom the blue lightnings played with an +incessant flicker. Then he bade those about him stand back, and calling +Owen and the prince to him, said: “Messenger, my son tells me that your +wisdom knows a plan whereby you may be preserved from the fury of the +tempest. Use it, I pray of you, Messenger, that your life may be saved, +and with it the life of the only son who is left to me.” + +“I cannot,” answered Owen, “for thus by doubting Him I should tempt my +Master. Still, it is not laid upon the prince to accompany through this +trial. Let him stay here, and I alone will stand beneath the cross.” + +“Stay, Nodwengo,” implored the old man. + +“I did not think to live to hear my father bid me, one of the royal +blood of the Amasuka, to desert my captain in the hour of battle and +hide myself in the grass like a woman,” answered the prince with a +bitter smile. “Nay, it may be that death awaits me yonder, but nothing +except death shall keep me back from the venture.” + +“It is well spoken,” said the king; “be it as you will.” + +Now the company of wizards, leaving their medicine-pots upon the ground, +formed themselves in a treble line, and marching to where the king +stood, they saluted him. Then they sang the praises of their god, and in +a song that had been prepared, heaped insult upon the God of the +white man and upon the messenger who preached Him. To all of this Owen +listened in silence. + +“He is a coward!” cried their spokesman; “he has not a word to say. He +skulks there in his white robes behind the majesty of the king. Let him +go forth and stand by his piece of wood. He dare not go! He thinks the +hillside safer. Come out, little White Man, and we will show you how +we manage the lightnings. Ah! they shall fly about you like spears in +battle. You shall throw yourself upon the ground and shriek in terror, +and then they will lick you up and you shall be no more, and there will +be an end of you and the symbol of your God.” + +“Cease your boastings,” said the king shortly, “and get you back to your +place, knowing that if it should chance that the white man conquers you +will be called upon to answer for these words.” + +“We shall be ready, O King,” they cried; and amidst the cheers of the +vast audience they marched back to their station, still singing the +blasphemous mocking song. + +Now to the west all the heavens were black as night, though the +eastern sky still showed blue and cloudless. Nature lay oppressed with +silence--silence intense and unnatural; and so great was the heat that +the air danced visibly above the ironstone as it dances about a glowing +stove. Suddenly the quietude was broken by a moaning sound of wind; +the grass stirred, the leaves of the trees began to shiver, and an icy +breath beat upon Owen’s brow. + +“Let us be going,” he said, and lifting the ivory crucifix above his +head, he passed the stream and walked towards the wooden cross. After +him came the Prince Nodwengo, wearing his royal dress of leopard skin, +and after him, John, arrayed in a linen robe. + +As the little procession appeared to their view some of the soldiers +began to mock, but almost instantly the laughter died away. Rude as +they were, these savages understood that here was no occasion for their +mirth, that the three men indeed seemed clothed with a curious dignity. +Perhaps it was their slow and quiet gait, perhaps a sense of the errand +upon which they were bound; or it may have been the strange unearthly +light that fell upon them from over the edge of the storm cloud; at the +least, as the multitude became aware, their appearance was impressive. +They reached the cross and took up their stations there, Owen in front +of it, Nodwengo to the right, and John to the left. + +Now a sharp squall of strong wind swept across the space, and with it +came a flaw of rain. It passed by, and the storm that had been muttering +and growling in the distance began to burst. The great clouds seemed to +grow and swell, and from the breast of them swift lightnings leapt, to +be met by other lightnings rushing upwards from the earth. The air was +filled with a tumult of uncertain wind and a hiss as of distant rain. +Then the batteries of thunder were opened, and the world shook with +their volume. Down from on high the flashes fell blinding and incessant, +and by the light of them the fire-doctors could be seen running to and +fro, pointing now here and now there with their wands of human bones, +and pouring the medicines from their gourds upon the ground and upon +each other. Owen and his two companions could be seen also, standing +quietly with clasped hands, while above them towered the tall white +cross. + +At length the storm was straight over head. Slowly it advanced in +its awe-inspiring might as flash after flash, each more fantastic and +horrible than the last, smote upon the floor of ironstone. It played +about the shapes of the doctors, who in the midst of it looked like +devils in an inferno. It crept onwards towards the station of the cross, +but--_it never reached the cross_. + +One flash struck indeed within fifty paces of where Owen stood. Then of +a sudden a marvel happened, or something which to this day the People of +Fire talk of as a marvel, for in an instant the rain began to pour like +a wall of water stretching from earth to heaven, and the wind changed. +It had been blowing from the west, now it blew from the east with the +force of a gale. + +It blew and rolled the tempest back upon itself, causing it to return +to the regions whence it had gathered. At the very foot of the cross +its march was stayed; there was the water-line, as straight as if it had +been drawn with a rule. The thunder-clouds that were pressed forward met +the clouds that were pressed back, and together they seemed to come +to earth, filling the air with a gloom so dense that the eye could not +pierce it. To the west was a wall of blackness towering to the heavens; +to the east, light, blue and unholy, gleamed upon the white cross and +the figures of its watchers. + +For some seconds--twenty or more--there was a lull, and then it +seemed as though all hell had broken loose upon the world. The wall of +blackness became a wall of flame, in which strange and ardent shapes +appeared ascending and descending; the thunder bellowed till the +mountains rocked, and in one last blaze, awful and indescribable, the +skies melted into a deluge of fire. In the flare of it Owen thought that +he saw the figures of men falling this way and that, then he staggered +against the cross for support and his senses failed him. + +***** + +When they returned again, he perceived the storm being drawn back from +the face of the pale earth like a pall from the face of the dead, and he +heard a murmur of fear and wonder rising from ten thousand throats. + +***** + +Well might they fear and wonder, for of the twenty and one wizards +eleven were dead, four were paralysed by shock, five were flying in +their terror, and one, Hokosa himself, stood staring at the fallen, a +very picture of despair. Nor was this all, for the meteor stone with a +human shape which for generations the People of Fire had worshipped as a +god, lay upon the plain in fused and shattered fragments. + +The people saw, and a sound as of a hollow groan of terror went up from +them. Then they were silent. For a while Owen and his companions were +silent also, since their hearts were too full for speech. Then he +said:-- + +“As the snake fell harmless from the hand of Paul, so has the lightning +turned back from me, who strive to follow in his footsteps, working +death and dismay among those who would have harmed us. May forgiveness +be theirs who were without understanding. Brethren, let us return and +make report to the king.” + +Now, as they had come, so they went back; first Owen with the crucifix, +next to him Nodwengo, and last of the three John. They drew near to the +king, when suddenly, moved by a common impulse, the thousands of the +people upon the banks of the stream with one accord threw themselves +upon their knees before Owen, calling him God and offering him worship. +Infected by the contagion, Umsuka, his guard and his councillors +followed their example, so that of all the multitude Hokosa alone +remained upon his feet, standing by his dishonoured and riven deity. + +“Rise!” cried Owen aghast. “Would you do sacrilege, and offer worship to +a man? Rise, I command you!” + +Then the king rose, saying:-- + +“You are no man, Messenger, you are a spirit.” + +“He is a spirit,” repeated the multitude after him. + +“I am _not_ a spirit, I am yet a man,” cried Owen again, “but the Spirit +Whom I serve has made His power manifest in me His servant, and your +idols are smitten with the sword of His power, O ye Sons of Fire! Hokosa +still lives, let him be brought hither.” + +They fetched Hokosa, and he stood before them. + +“You have seen, Wizard,” said the king. “What have you to say?” + +“Nothing,” answered Hokosa, “save that victory is to the Cross, and to +the white man who preaches it, for his magic is greater than our magic. +By his command the tempest was stayed, and the boasts we hurled fell +back upon our heads and the head of our god to destroy us.” + +“Yes,” said the king, “victory is to the Cross, and henceforth the Cross +shall be worshipped in this land, or at least no other god shall be +worshipped. Let us be going. Come with me, Messenger, Lord of the +Lightning.” + + + +CHAPTER XI + +THE WISDOM OF THE DEAD + +On the morrow Owen baptised the king, many of his councillors, and +some twenty others whom he considered fit to receive the rite. Also he +despatched his first convert John, with other messengers, on a three +months’ journey to the coast, giving them letters acquainting the bishop +and others with his marvellous success, and praying that missionaries +might be sent to assist him in his labours. + +Now day by day the Church grew till it numbered hundreds of souls, and +thousands more hovered on its threshold. From dawn to dark Owen toiled, +preaching, exhorting, confessing, gathering in his harvest; and from +dark to midnight he pored over his translation of the Scriptures, +teaching Nodwengo and a few others how to read and write them. But +although his efforts were crowned with so signal and extraordinary a +triumph, he was well aware of the dangers that threatened the life of +the infant Church. Many accepted it indeed, and still more tolerated +it; but there remained multitudes who regarded the new religion with +suspicion and veiled hatred. Nor was this strange, seeing that the +hearts of men are not changed in an hour or their ancient customs easily +overset. + +On one point, indeed, Owen had to give way. The Amasuka were a +polygamous people; all their law and traditions were interwoven with +polygamy, and to abolish that institution suddenly and with violence +would have brought their social fabric to the ground. Now, as he knew +well, the missionary Church declares in effect that no man can be both a +Christian and a polygamist; therefore among the followers of that custom +the missionary Church makes but little progress. Not without many qualms +and hesitations, Owen, having only the Scriptures to consult, came to a +compromise with his converts. If a man already married to more than one +wife wished to become a Christian, he permitted him to do so upon the +condition that he took no more wives; while a man unmarried at the time +of his conversion might take one wife only. This decree, liberal as it +was, caused great dissatisfaction among both men and women. But it was +as nothing compared to the feeling that was evoked by Owen’s preaching +against all war not undertaken in self-defence, and against the strict +laws which he prevailed upon the king to pass, suppressing the practice +of wizardry, and declaring the chief or doctor who caused a man to +be “smelt out” and killed upon charges of witchcraft to be guilty of +murder. + +At first whenever Owen went abroad he was surrounded by thousands of +people who followed him in the expectation that he would work miracles, +which, after his exploits with the lightning, they were well persuaded +that he could do if he chose. But he worked no more miracles; he only +preached to them a doctrine adverse to their customs and foreign to +their thoughts. + +So it came about that in time, when the novelty was gone off and the +story of his victory over the Fire-god had grown stale, although the +work of conversion went on steadily, many of the people grew weary of +the white man and his doctrines. Soon this weariness found expression in +various ways, and in none more markedly than by the constant desertions +from the ranks of the king’s regiments. At first, by Owen’s advice, +the king tolerated these desertions; but at length, having obtained +information that an entire regiment purposed absconding at dawn, +he caused it to be surrounded and seized by night. Next morning he +addressed that regiment, saying:-- + +“Soldiers, you think that because I have become a Christian and will not +permit unnecessary bloodshed, I am also become a fool. I will teach +you otherwise. One man in every twenty of you shall be killed, and +henceforth any soldier who attempts to desert will be killed also!” + +The order was carried out, for Owen could not find a word to say against +it, with the result that desertions almost ceased, though not before the +king had lost some eight or nine thousand of his best soldiers. Worst of +all, these soldiers had gone to join Hafela in his mountain fastnesses; +and the rumour grew that ere long they would appear again, to claim the +crown for him or to take it by force of arms. + +Now too a fresh complication arose. The old king sickened of his last +illness, and soon it became known that he must die. A month later die +he did, passing away peacefully in Owen’s arms, and with his last +breath exhorting his people to cling to the Christian religion; to take +Nodwengo for their king and to be faithful to him. + +The king died, and that same day was buried by Owen in the gloomy +resting-place of the blood-royal of the People of Fire, where a +Christian priest now set foot for the first time. + +On the morrow Nodwengo was proclaimed king with much ceremony in face of +the people and of all the army that remained to him. One captain raised +a cry for Hafela his brother. Nodwengo caused him to be seized and +brought before him. + +“Man,” he said, “on this my coronation day I will not stain my hand with +blood. Listen. You cry upon Hafela, and to Hafela you shall go, taking +him this message. Tell him that I, Nodwengo, have succeeded to the crown +of Umsuka, my father, by his will and the will of the people. Tell him +it is true that I have become a Christian, and that Christians follow +not after war but peace. Tell him, however, that though I am a Christian +I have not forgotten how to fight or how to rule. It has reached my +ears that it is his purpose to attack me with a great force which he is +gathering, and to possess himself of my throne. If he should choose to +come, I shall be ready to meet him; but I counsel him against coming, +for it will be to find his death. Let him stay where he is in peace, and +be my subject; or let him go afar with those that cleave to him, and set +up a kingdom of his own, for then I shall not follow him; but let him +not dare to lift a spear against me, his sovereign, since if he does so +he shall be treated as a rebel and find the doom of a rebel. Begone, and +show your face here no more!” + +The man crept away crestfallen; but all who heard that speech broke into +cheering, which, as its purport was repeated from rank to rank, spread +far and wide; for now the army learned that in becoming a Christian, +Nodwengo had not become a woman. Of this indeed he soon gave them ample +proof. The old king’s grip upon things had been lax, that of Nodwengo +was like iron. He practised no cruelties, and did injustice to none; +but his discipline was severe, and soon the regiments were brought to a +greater pitch of proficiency than they had ever reached before, although +they were now allowed to marry when they pleased, a boon that hitherto +had been denied to them. Moreover, by Owen’s help, he designed an +entirely new system of fortification of the kraal and surrounding hills, +which would, it was thought, make the place impregnable. These and many +other acts, equally vigorous and far-seeing, put new heart into the +nation. Also the report of them put fear into Hafela, who, it was +rumoured, had now given up all idea of attack. + +Some there were, however, who looked upon these changes with little +love, and Hokosa was one of them. After his defeat in the duel by fire, +for a while his spirit was crushed. Hitherto he had more or less been +a believer in the protecting influence of his own god or fetish, who +would, as he thought, hold his priests scatheless from the lightning. +Often and often had he stood in past days upon that plain while the +great tempests broke around his head, and returned thence unharmed, +attributing to sorcery a safety that was really due to chance. From time +to time indeed a priest was killed; but, so his companions held, the +misfortune resulted invariably from the man’s neglect of some rite, or +was a mark of the anger of the heavens. + +Now Hokosa had lived to see all these convictions shattered: he had seen +the lightning, which he pretended to be able to control, roll back +upon him from the foot of the Christian cross, reducing his god to +nothingness and his companions to corpses. + +At first Hokosa was dismayed, but as time went on hope came back to him. +Stripped of his offices and power, and from the greatest in the nation, +after the king, become one of small account, still no harm or violence +was attempted towards him. He was left wealthy and in peace, and living +thus he watched and listened with open eyes and ears, waiting till the +tide should turn. It seemed that he would not have long to wait, for +reasons that have been told. + +“Why do you sit here like a vulture on a rock,” asked the girl Noma, +whom he had taken to wife, “when you might be yonder with Hafela, +preparing him by your wisdom for the coming war?” + +“Because I am a king-vulture, and I wait for the sick bull to die,” he +answered, pointing to the Great Place beneath him. “Say, why should I +bring Hafela to prey upon a carcase I have marked down for my own?” + +“Now you speak well,” said Noma; “the bull suffers from a strange +disease, and when he is dead another must lead the herd.” + +“That is so,” answered her husband, “and, therefore, I am patient.” + +It was shortly after this conversation that the old king died, with +results very different from those which Hokosa had anticipated. Although +he was a Christian, to his surprise Nodwengo showed that he was also a +strong ruler, and that there was little chance of the sceptre slipping +from his hand--none indeed while the white teacher was there to guide +him. + +“What will you do now, Hokosa?” asked Noma his wife upon a certain day. +“Will you turn to Hafela after all?” + +“No,” answered Hokosa; “I will consult my ancient lore. Listen. Whatever +else is false, this is true: that magic exists, and I am its master. For +a while it seemed to me that the white man was greater at the art than I +am; but of late I have watched him and listened to his doctrines, and I +believe that this is not so. It is true that in the beginning he read my +plans in a dream, or otherwise; it is true that he hurled the lightning +back upon my head; but I hold that these things were accidents. Again +and again he has told us that he is not a wizard; and if this be so, he +can be overcome.” + +“How, husband?” + +“How? By wizardry. This very night, Noma, with your help I will consult +the dead, as I have done in bygone time, and learn the future from their +lips which cannot lie.” + +“So be it; though the task is hateful to me, and I hate you who force me +to it.” + +Noma answered thus with passion, but her eyes shone as she spoke: for +those who have once tasted the cup of magic are ever drawn to drink of +it again, even when they fear the draught. + +**** + +It was midnight, and Hokosa with his wife stood in the burying-ground of +the kings of the Amasuka. Before Owen came upon his mission it was death +to visit this spot except upon the occasion of the laying to rest of one +of the royal blood, or to offer the annual sacrifice to the spirits +of the dead. Even beneath the bright moon that shone upon it the place +seemed terrible. Here in the bosom of the hills was an amphitheatre, +surrounded by walls of rock varying from five hundred to a thousand feet +in height. In this amphitheatre grew great mimosa thorns, and above +them towered pillars of granite, set there not by the hand of man but by +nature. It would seem that the Amasuka, led by some fine instinct, had +chosen these columns as fitting memorials of their kings, at the least a +departed monarch lay at the foot of each of them. + +The smallest of these unhewn obelisks--it was about fifty feet +high--marked the resting-place of Umsuka; and deep into its granite +Owen with his own hand had cut the dead king’s name and date of death, +surmounting his inscription with a symbol of the cross. + +Towards this pillar Hokosa made his way through the wet grass, followed +by Noma his wife. Presently they were there, standing one upon each +side of a little mound of earth more like an ant-heap than a grave; for, +after the custom of his people, Umsuka had been buried sitting. At the +foot of each of the pillars rose a heap of similar shape, but many +times as large. The kings who slept there were accompanied to their +resting-places by numbers of their wives and servants, who had +been slain in solemn sacrifice that they might attend their Lord +whithersoever he should wander. + +“What is that you desire and would do?” asked Noma, in a hushed voice. +Bold as she was, the place and the occasion awed her. + +“I desire wisdom from the dead!” he answered. “Have I not already told +you, and can I not win it with your help?” + +“What dead, husband?” + +“Umsuka the king. Ah! I served him living, and at the last he drove me +away from his side. Now he shall serve me, and out of the nowhere I will +call him back to mine.” + +“Will not this symbol defeat you?” and Noma pointed at the cross hewn in +the granite. + +At her words a sudden gust of rage seemed to shake the wizard. His still +eyes flashed, his lips turned livid, and with them he spat upon the +cross. + +“It has no power,” he said. “May it be accursed, and may he who believes +therein hang thereon! It has no power; but even if it had, according to +the tale of that white liar, such things as I would do have been done +beneath its shadow. By it the dead have been raised--ay! dead kings have +been dragged from death and forced to tell the secrets of the grave. +Come, come, let us to the work.” + +“What must I do, husband?” + +“You shall sit you there, even as a corpse sits, and there for a little +while you shall die--yes, your spirit shall leave you--and I will fill +your body with the soul of him who sleeps beneath; and through your +lips I will learn his wisdom, to whom all things are known.” + +“It is terrible! I am afraid!” she said. “Cannot this be done +otherwise?” + +“It cannot,” he answered. “The spirits of the dead have no shape or +form; they are invisible, and can speak only in dreams or through the +lips of one in whose pulses life still lingers, though soul and body be +already parted. Have no fear. Ere his ghost leaves you it shall recall +your own, which till the corpse is cold stays ever close at hand. I did +not think to find a coward in you, Noma.” + +“I am not a coward, as you know well,” she answered passionately, “for +many a deed of magic have we dared together in past days. But this is +fearsome, to die that my body may become the home of the ghost of a +dead man, who perchance, having entered it, will abide there, leaving +my spirit houseless, or perchance will shut up the doors of my heart in +such fashion that they never can be opened. Can it not be done by trance +as aforetime? Tell me, Hokosa, how often have you thus talked with the +dead?” + +“Thrice, Noma.” + +“And what chanced to them through whom you talked?” + +“Two lived and took no harm; the third died, because the awakening +medicine lacked power. Yet fear nothing; that which I have with me is of +the best. Noma, you know my plight: I must win wisdom or fall for ever, +and you alone can help me; for under this new rule, I can no longer buy +a youth or maid for purposes of witchcraft, even if one could be found +fitted to the work. Choose then: shall we go back or forward? Here +trance will not help us; for those entranced cannot read the future, nor +can they hold communion with the dead, being but asleep. Choose, Noma.” + +“I have chosen,” she answered. “Never yet have I turned my back upon a +venture, nor will I do so now. Come life, come death, I will submit me +to your wish, though there are few women who would dare as much for any +man. Nor in truth do I do this for you, Hokosa; I do it because I seek +power, and thus only can we win it who are fallen. Also I love all +things strange, and desire to commune with the dead and to know that, +if for some few minutes only, at least my woman’s breast has held the +spirit of a king. Yet, I warn you, make no fault in your magic; for +should I die beneath it, then I, who desire to live on and to be great, +will haunt you and be avenged upon you!” + +“Oh! Noma,” he said, “if I believed that there was any danger for you, +should I ask you to suffer this thing?--I, who love you more even than +you love power, more than my life, more than anything that is or ever +can be.” + +“I know it, and it is to that I trust,” the woman answered. “Now begin, +before my courage leaves me.” + +“Good,” he said. “Seat yourself there upon the mound, resting your head +against the stone.” + +She obeyed; and taking thongs of hide which he had made ready, Hokosa +bound her wrists and ankles, as these people bind the wrists and ankles +of corpses. Then he knelt before her, staring into her face with his +solemn eyes and muttering: “Obey and sleep.” + +Presently her limbs relaxed, and her head fell forward. + +“Do you sleep?” he asked. + +“I sleep. Whither shall I go? It is the true sleep--test me.” + +“Pass to the house of the white man, my rival. Are you with him?” + +“I am with him.” + +“What does he?” + +“He lies in slumber on his bed, and in his slumber he mutters the name +of a woman, and tells her that he loves her, but that duty is more than +love. Oh! call me back I cannot stay; a Presence guards him, and thrusts +me thence.” + +“Return,” said Hokosa starting. “Pass through the earth beneath you and +tell me what you see.” + +“I see the body of the king; but were it not for his royal ornaments +none would know him now.” + +“Return,” said Hokosa, “and let the eyes of your spirit be open. Look +around you and tell me what you see.” + +“I see the shadows of the dead,” she answered; “they stand about you, +gazing at you with angry eyes; but when they come near you, something +drives them back, and I cannot understand what it is they say.” + +“Is the ghost of Umsuka among them?” + +“It is among them.” + +“Bid him prophesy the future to me.” + +“I have bidden him, but he does not answer. If you would hear him +speak, it must be through the lips of my body; and first my body must be +emptied of my ghost, that his may find a place therein.” + +“Say, can his spirit be compelled?” + +“It can be compelled, or that part of it which still hover near this +spot, if you dare to speak the words you know. But first its house +must be made ready. Then the words must be spoken, and all must be done +before a man can count three hundred; for should the blood begin to clot +about my heart, it will be still for ever.” + +“Hearken,” said Hokosa. “When the medicine that I shall give does its +work, and the spirit is loosened from your body, let it not go afar, no, +whatever tempts or threatens it, and suffer not that the death-cord be +severed, lest flesh and ghost be parted for ever.” + +“I hear, and I obey. Be swift, for I grow weary.” + +Then Hokosa took from his pouch two medicines: one a paste in a box, the +other a fluid in a gourd. Taking of the paste he knelt upon the grave +before the entranced woman and swiftly smeared it upon the mucous +membrane of the mouth and throat. Also he thrust pellets of it into the +ears, the nostrils, and the corners of the eyes. + +The effect was almost instantaneous. A change came over the girl’s +lovely face, the last awful change of death. Her cheeks fell in, her +chin dropped, her eyes opened, and her flesh quivered convulsively. The +wizard saw it all by the bright moonlight. Then he took up his part in +this unholy drama. + +All that he did cannot be described, because it is indescribable. The +Witch of Endor repeated no formula, but she raised the dead; and so did +Hokosa the wizard. But he buried his face in the grey dust of the grave, +he blew with his lips into the dust, he clutched at the dust with his +hands, and when he raised his face again, lo! it was grey like the +dust. Now began the marvel; for, though the woman before him remained a +corpse, from the lips of that corpse a voice issued, and its sound +was horrible, for the accent and tone of it were masculine, and the +instrument through which it spoke--Noma’s throat--was feminine. Yet it +could be recognised as the voice of Umsuka the dead king. + +“Why have you summoned me from my rest, Hokosa?” muttered the voice from +the lips of the huddled corpse. + +“Because I would learn the future, Spirit of the king,” answered the +wizard boldly, but saluting as he spoke. “You are dead, and to your +sight all the Gates are opened. By the power that I have, I command you +to show me what you see therein concerning myself, and to point out to +me the path that I should follow to attain my ends and the ends of her +in whose breast you dwell.” + +At once the answer came, always in the same horrible voice:-- + +“Hearken to your fate for this world, Hokosa the wizard. You shall +triumph over your rival, the white man, the messenger; and by your hand +he shall perish, passing to his appointed place where you must meet +again. By that to which you cling you shall be betrayed, ah! you shall +lose that which you love and follow after that which you do not desire. +In the grave of error you shall find truth, from the deeps of sin you +shall pluck righteousness. When these words fall upon your ears again, +then, Wizard, take them for a sign and let your heart be turned. That +which you deem accursed shall lift you up on high. High shall you be +set above the nation and its king, and from age to age the voice of the +people shall praise you. Yet in the end comes judgment; and there shall +the sin and the atonement strive together, and in that hour, Wizard, you +shall----” + +Thus the voice spoke, strongly at first, but growing ever more feeble as +the sparks of life departed from the body of the woman, till at length +it ceased altogether. + +“What shall chance to me in that hour?” Hokosa asked eagerly, placing +his ears against Noma’s lips. + +No answer came; and the wizard knew that if he would drag his wife back +from the door of death he must delay no longer. Dashing the sweat from +his eyes with one hand, with the other he seized the gourd of fluid +that he had placed ready, and thrusting back her head, he poured of its +contents down her throat and waited a while. She did not move. In an +extremity of terror he snatched a knife, and with a single cut severed +a vein in her arm, then taking some of the fluid that remained in the +gourd in his hand, he rubbed it roughly upon her brow and throat and +heart. Now Noma’s fingers stirred, and now, with horrible contortions +and every symptom of agony, life returned to her. The blood flowed from +her wounded arm, slowly at first, then more fast, and lifting her head +she spoke. + +“Take me hence,” she cried, “or I shall go mad; for I have seen and +heard things too terrible to be spoken!” + +“What have you seen and heard?” he asked, while he cut the thongs which +bound her wrists and feet. + +“I do not know,” Noma answered weeping; “the vision of them passes +from me; but all the distances of death were open to my sight; yes, I +travelled through the distances of death. In them I met him who was the +king, and he lay cold within me, speaking to my heart; and as he passed +from me he looked upon the child which I shall bear and cursed it, and +surely accursed it shall be. Take me hence, O you most evil man, for of +your magic I have had enough, and from this day forth I am haunted!” + +“Have no fear,” answered Hokosa; “you have made the journey whence but +few return; and yet, as I promised you, you have returned to wear the +greatness you desire and that I sent you forth to win; for henceforth +we shall be great. Look, the dawn is breaking--the dawn of life and the +dawn of power--and the mists of death and of disgrace roll back before +us. Now the path is clear, the dead have shown it to me, and of wizardry +I shall need no more.” + +“Ay!” answered Noma, “but night follows dawn as the dawn follows +night; and through the darkness and the daylight, I tell you, Wizard, +henceforth I am haunted! Also, be not so sure, for though I know not +what the dead have spoken to you, yet it lingers on my mind that their +words have many meanings. Nay, speak to me no more, but let us fly from +this dread home of ghosts, this habitation of the spirit-folk which we +have violated.” + +So the wizard and his wife crept from that solemn place, and as they +went they saw the dawn-beams lighting upon the white cross that was +reared in the Plain of Fire. + + + +CHAPTER XII + +THE MESSAGE OF HOKOSA + +The weeks passed by, and Hokosa sat in his kraal weaving a great plot. +None suspected him any more, for though he did not belong to it, he was +heard to speak well of the new faith, and to acknowledge that the god of +fire which he had worshipped was a false god. He was humble also towards +the king, but he craved to withdraw himself from all matters of the +State, saying that now he had but one desire--to tend his herds and +garden, and to grow old in peace with the new wife whom he had chosen +and whom he loved. Owen, too, he greeted courteously when he met him, +sending him gifts of corn and cattle for the service of his church. +Moreover, when a messenger came from Hafela, making proposals to him, he +drove him away and laid the matter before the council of the king. Yet +that messenger, who was hunted from the kraal, took back a secret word +for Hafela’s ear. + +“It is not always winter,” was the word, “and it may chance that in +the springtime you shall hear from me.” And again, “Say to the Prince +Hafela, that though my face towards him is like a storm, yet behind the +clouds the sun shines ever.” + +At length there came a day when Noma, his wife, was brought to bed. +Hokosa, her husband, tended her alone, and when the child was born +he groaned aloud and would not suffer her to look upon its face. Yet, +lifting herself, she saw. + +“Did I not tell you it was accursed?” she wailed. “Take it away!” and +she sank back in a swoon. So he took the child, and buried it deep in +the cattle-yard by night. + +After this it came about that Noma, who, though her mind owned the sway +of his, had never loved him over much, hated her husband Hokosa. Yet he +had this power over her that she could not leave him. But he loved her +more and more, and she had this power over him that she could always +draw him to her. Great as her beauty had ever been, after the birth of +the child it grew greater day by day, but it was an evil beauty, the +beauty of a witch; and this fate fell upon her, that she feared the dark +and would never be alone after the sun had set. + +When she was recovered from her illness, Noma sat one night in her hut, +and Hokosa sat there also watching her. The evening was warm, but a +bright fire burned in the hut, and she crouched upon a stool by the +fire, glancing continually over her shoulder. + +“Why do you bide by the fire, seeing that it is so hot, Noma?” he asked. + +“Because I fear to be away from the light,” she answered; adding, “Oh, +accursed man! for your own ends you have caused me to be bewitched, ah! +and that which was born of me also, and bewitched I am by those shadows +that you bade me seek, which now will never leave me. Nor, is this all. +You swore to me that if I would do your will I should become great, ay! +and you took me from one who would have made me great and whom I should +have pushed on to victory. But now it seems that for nothing I made that +awful voyage into the deeps of death; and for nothing, yet living, am +I become the sport of those that dwell there. How am I greater than I +was--I who am but the second wife of a fallen witch-doctor, who sits +in the sun, day by day, while age gathers on his head like frost upon a +bush? Where are all your high schemes now? Where is the fruit of wisdom +that I gathered for you? Answer, Wizard, whom I have learned to hate, +but from whom I cannot escape!” + +“Truly,” said Hokosa in a bitter voice, “for all my sins against them +the heavens have laid a heavy fate upon my head, that thus with flesh +and spirit I should worship a woman who loathes me. One comfort only is +left to me, that you dare not take my life lest another should be added +to those shadows who companion you, and what I bid you, that you must +still do. Ay, you fear the dark, Noma; yet did I command you to rise +and go stand alone through the long night yonder in the burying-place of +kings, why, you must obey. Come, I command you--go!” + +“Nay, nay!” she wailed in an extremity of terror. Yet she rose and +went towards the door sideways, for her hands were outstretched in +supplication to him. + +“Come back,” he said, “and listen: If a hunter has nurtured up a fierce +dog, wherewith alone he can gain his livelihood, he tries to tame that +dog by love, does he not? And if it will not become gentle, then, the +brute being necessary to him, he tames it by fear. I am the hunter and, +Noma, you are the hound; and since this curse is on me that I cannot +live without you, why I must master you as best I may. Yet, believe me, +I would not cause you fear or pain, and it saddens me that you should +be haunted by these sick fancies, for they are nothing more. I have seen +such cases before to-day, and I have noted that they can be cured by +mixing with fresh faces and travelling in new countries. Noma, I think +it would be well that, after your late sickness, according to the custom +of the women of our people, you should part from me a while, and go upon +a journey of purification.” + +“Whither shall I go and who will go with me?” she asked sullenly. + +“I will find you companions, women discreet and skilled. And as to +where you shall go, I will tell you. You shall go upon an embassy to the +Prince Hafela.” + +“Are you not afraid that I should stop there?” she asked again, with a +flash of her eyes. “It is true that I never learned all the story, yet +I thought that the prince was not so glad to hand me back to you as you +would have had me to believe. The price you paid for me must have been +good, Hokosa, and mayhap it had to do with the death of a king.” + +“I am not afraid,” he answered, setting his teeth, “because I know that +whatever your heart may desire, my will follows you, and while I live +that is a cord you cannot break unless I choose to loose it, Noma. I +command you to be faithful to me and to return to me, and these commands +you must obey. Hearken: you taunted me just now, saying that I sat like +a dotard in the sun and advanced you nothing. Well, I will advance you, +for both our sakes, but mostly for your own, since you desire it, and it +must be done through the Prince Hafela. I cannot leave this kraal, for +day and night I am watched, and before I had gone an hour’s journey +I should be seized; also here I have work to do. But the Place of +Purification is secret, and when you reach it you need not bide there, +you can travel on into the mountains till you come to the town of the +Prince Hafela. He will receive you gladly, and you shall whisper this +message in his ear:-- + +“‘These are the words of Hokosa, my husband, which he has set in my +mouth to deliver to you, O Prince. Be guided by them and grow great; +reject them and die a wanderer, a little man of no account. But first, +this is the price that you shall swear by the sacred oath to pay to +Hokosa, if his wisdom finds favour in your sight and through it you come +to victory: That after you, the king, he, Hokosa, shall be the first man +in our land, the general of the armies, the captain of the council, the +head of the doctors, and that to him shall be given half the cattle of +Nodwengo, who now is king. Also to him shall be given power to stamp out +the new faith which overruns the land like a foreign weed, and to deal +as he thinks fit with those who cling thereto.’ + +“Now, Noma, when he has sworn this oath in your ear, calling down ruin +upon his own head, should he break one word of it, and not before, you +shall continue the message thus: ‘These are the other words that Hokosa +set in my mouth: “Know, O Prince, that the king, your brother, grows +very strong, for he is a great soldier, who learned his art in bygone +wars; also the white man that is named Messenger has taught him many +things as to the building of forts and walls and the drilling and +discipline of men. So strong is he that you can scarcely hope to conquer +him in open war--yet snakes may crawl where men cannot walk. Therefore, +Prince, let your part be that of a snake. Do you send an embassy to the +king, your brother and say to him:-- + +“‘My brother, you have been preferred before me and set up to be king in +my place, and because of this my heart is bitter, so bitter that I have +gathered my strength to make war upon you. Yet, at the last, I have +taken another council, bethinking me that, if we fight, in the end it +may chance that neither of us will be left alive to rule, and that the +people also will be brought to nothing. To the north there lies a good +country and a wide, where but few men live, and thither I would go, +setting the mountains and the river between us; for there, far beyond +your borders, I also can be a king. Now, to reach this country, I must +travel by the pass that is not far from your Great Place, and I pray +you that you will not attack my _impis_ or the women and children that I +shall send, and a guard before them, to await me in the plain beyond the +mountains, seeing that these can only journey slowly. Let us pass by in +peace, my brother, for so shall our quarrel be ended; but if you do so +much as lift a single spear against me, then I will give you battle, +setting my fortune against your fortune and my god against your God!’ + +“Such are the words that the embassy shall deliver into the ears of +the king, Nodwengo, and it shall come about that when he hears them, +Nodwengo, whose heart is gentle and who seeks not war, shall answer +softly, saying:-- + +“‘Go in peace, my brother, and live in peace in that land which you +would win.’ + +“Then shall you, Hafela, send on the most of your cattle and the women +and the children through that pass in the mountains, bidding them to +await you in the plain, and after a while you shall follow them with +your _impis_. But these shall not travel in war array, for carriers must +bear their fighting shields in bundles and their stabbing spears shall +be rolled up in mats. Now, on the sixth day of your journey you shall +camp at the mouth of the pass which the cattle and the women have +already travelled, and his outposts and spies will bring it to the ears +of the king that your force is sleeping there, purposing to climb the +pass on the morrow. + +“But on that night, so soon as the darkness falls, you must rise up with +your captains and your regiments, leaving your fires burning and men +about your fires, and shall travel very swiftly across the valley, so +that an hour before the dawn you reach the second range of mountains, +and pass it by the gorge which is the burying-place of kings. Here you +shall light a fire, which those who watch will believe to be but the +fire of a herdsman who is acold. But I, Hokosa, also shall be watching, +and when I see that fire I will creep, with some whom I can trust, to +the little northern gate of the outer wall, and we will spear those +that guard it and open the gate, that your army may pass through. Then, +before the regiments can stand to their arms or those within it are +awakened, you must storm the inner walls and by the light of the burning +huts, put the dwellers in the Great Place to the spear, and the rays of +the rising sun shall crown you king. + +“Follow this counsel of mine, O Prince Hafela, and all will go well +with you. Neglect it and be lost. There is but one thing which you need +fear--it is the magic of the Messenger, to whom it is given to read the +secret thoughts of men. But of him take no account, for he is my charge, +and before ever you set a foot within the Great Place he shall have +taken his answer back to Him Who sent him.” + +Hokosa finished speaking. + +“Have you heard?” he said to Noma. + +“I have heard.” + +“Then speak the message.” + +She repeated it word for word, making no fault. “Have no fear,” she +added, “I shall forget nothing when I stand before the prince.” + +“You are a woman, but your counsel is good. What think you of the plan, +Noma?” + +“It is deep and well laid,” she answered, “and surely it would succeed +were it not for one thing. The white man, Messenger, will be too clever +for you, for as you say, he is a reader of the thoughts of men.” + +“Can the dead read men’s thoughts, or if they can, do they cry them on +the market-place or into the ears of kings?” asked Hokosa. “Have I not +told you that, before I see the signal-fire yonder, the Messenger shall +sleep sound? I have a medicine, Noma, a slow medicine that none can +trace.” + +“The Messenger may sleep sound, Hokosa, and yet perchance he may pass +on his message to another and, with it, his magic. Who can say? Still, +husband, strike on for power and greatness and revenge, letting the blow +fall where it will.” + + + +CHAPTER XIII + +THE BASKET OF FRUIT + +Three days later it was announced that according to the custom of the +women of the People of Fire, Noma having given birth to a still-born +child, was about to start upon a journey to the Mount of Purification. +Here she would abide awhile and make sacrifice to the spirits of her +ancestors, that they might cease to be angry with her and in future +protect her from such misfortunes. This not unusual domestic incident +excited little comment, although it was remarked that the four matrons +by whom she was to be accompanied, in accordance with the tribal +etiquette, were all of them the wives of soldiers who had deserted to +Hafela. Indeed, the king himself noticed as much when Hokosa made the +customary formal application to him to sanction the expedition. + +“So be it,” he said, “though myself I have lost faith in such rites. +Also, Hokosa, I think it likely that although your wife goes out with +company, she will return alone.” + +“Why, King?” asked Hokosa. + +“For this reason--that those who travel with her have husbands yonder at +the town of the Prince Hafela, and the Mount of Purification is on the +road thither. Having gone so far, they may go farther. Well, let +them go, for I desire to have none among my people whose hearts turn +otherwhere, and it would not be wonderful if they should choose to seek +their lords. But perchance, Hokosa, there are some in this town who may +use them as messengers to the prince”--and he looked at him keenly. + +“I think not, King,” said Hokosa. “None but a fool would make use of +women to carry secret words or tidings. Their tongues are too long and +their memories too bad, or too uncertain.” + +“Yet I have heard, Hokosa, that you have made use of women in many a +strange work. Say now, what were you doing upon a night a while ago +with that fair witch-wife if yours yonder in the burying-place of kings, +where it is not lawful that you should set your foot? Nay, deny it not. +You were seen to enter the valley after midnight and to return thence +at the dawn, and it was seen also that as she came homewards your wife +walked as one who is drunken, and she, whom it is not easy to frighten, +wore a face of fear. Man, I do not trust you, and were I wise I should +hunt you hence, or keep you so close that you could scarcely move +without my knowledge. + +“Why should I trust you?” Nodwengo went on vehemently. “Can a wizard +cease from wizardry, or a plotter from his plots? No, not until the +waters run upward and the sun shines at night; not until repentance +touches you and your heart is changed, which I should hold as much a +marvel. You were my father’s friend and he made you great; yet you could +plan with my brother to poison him, your king. Nay, be silent; I know +it, though I have said nothing of it because one that is dear to me has +interceded for you. You were the priest of the false god, and with that +god are fallen from your place, yet you have not renounced him. You sit +still in your kraal and pretend to be asleep, but your slumber is that +of the serpent which watches his time to strike. How do I know that +you will not poison me as you would have poisoned my father, or stir up +rebellion against me, or bring my brother’s _impis_ on my head?” + +“If the King thinks any of these things of his servant,” answered Hokosa +in a humble voice, but with dignity, “his path is plain: let him put me +to death and sleep in peace. Who am I that I should full the ears of +a king with my defence against these charges, or dare to wrangle with +him?” + +“Long ago I should have put you to death, Hokosa,” answered Nodwengo +sternly, “had it not been that one has pleaded for you, declaring that +in you there is good which will overcome the evil, and that you who now +are an axe to cut down my throne, in time to come shall be a roof-tree +for its support. Also, the law that I obey does not allow me to take +the blood of men save upon full proof, and against you as yet I have +no proof. Still, Hokosa, be warned in time and let your heart be turned +before the grave claims your body and the Wicked One your soul.” + +“I thank you, King, for your gentle words and your tender care for my +well-being both on earth and after I shall leave it. But I tell you, +King, that I had rather die as your father would have killed me in the +old days, or your brother would kill me now, did either of them hate or +fear me, than live on in safety, owing my life to a new law and a new +mercy that do not befit the great ones of the world. King, I am your +servant,” and giving him the royal salute, Hokosa rose and left his +presence. + +“At the least there goes a man,” said Nodwengo, as he watched him +depart. + +“Of whom do you speak, King?” asked Owen, who at that moment entered the +royal house. + +“Of him whom you must have touched in the door-way, Messenger, Hokosa +the wizard,” answered the king, and he told him of what had passed +between them. “I said,” he added, “that he was a man, and so he is; yet +I hold that I have done wrong to listen to your pleading and to spare +him, for I am certain that he will bring bloodshed upon me and trouble +on the Faith. Think now, Messenger, how full must be that man’s heart of +secret rage and hatred, he who was so great and is now so little! Will +he not certainly strive to grow great again? Will he not strive to be +avenged upon those who humbled him and the religion they have chosen?” + +“It may be,” answered Owen, “but if so, he will not conquer. I tell +you, King, that like water hidden in a rock there is good in this man’s +heart, and that I shall yet find a rod wherewith to cause it to gush out +and refresh the desert.” + +“It is more likely that he will find a spear wherewith to cause your +blood to gush out and refresh the jackals,” answered the king grimly; +“but be it as you will. And now, what of your business?” + +“This, King: John, my servant, has returned from the coast countries, +and he brings me a letter saying that before long three white teachers +will follow him to take up the work which I have begun. I pray that when +they come, for my sake and for the sake of the truth that I have taught +you, you will treat them kindly and protect them, remembering that at +first they can know little of your language or your customs.” + +“I will indeed,” said the king, with much concern. “But tell me, +Messenger, why do you speak of yourself as of one who soon will be but a +memory? Do you purpose to leave us?” + +“No, King, but I believe that ere long I shall be recalled. I have given +my message, my task is well-nigh ended and I must be turning home. Save +for your sakes I do not sorrow at this, for to speak truth I grow very +weary,” and he smiled sadly. + +***** + +Hokosa went home alarmed and full of bitterness, for he had never +guessed that the “servant of the Messenger,” as he called Nodwengo the +King, knew so much about him and his plans. His fall was hard to him, +but to be thus measured up, weighed, and contemptuously forgiven was +almost more than he could bear. It was the white prophet who had done +this thing; he had told Nodwengo of his, Hokosa’s, share in the plot to +murder the late King Umsuka, though how he came to know of that matter +was beyond guessing. He had watched him, or caused him to be watched, +when he went forth to consult spirits in the place of the dead; he had +warned Nodwengo against him. Worst of all, he had dared to treat him +with contempt; had pleaded for his life and safety, so that he was +spared as men spare a snake from which the charmer has drawn the fangs. +When they met in the gate of the king’s house yonder this white thief, +who had stolen his place and power, had even smiled upon him and greeted +him kindly, and doubtless while he smiled, by aid of the magic he +possessed, had read him through and gone on to tell the story to the +king. Well, of this there should be an end; he would kill the Messenger, +or himself be killed. + +When Hokosa reached his kraal he found Noma sitting beneath a fruit tree +that grew in it, idly employed in stringing beads, for the work of the +household she left to his other wife, Zinti, an old and homely woman who +thought more of the brewing of the beer and the boiling of the porridge +than of religions or politics or of the will of kings. Of late Noma had +haunted the shadow of this tree, for beneath it lay that child which had +been born to her. + +“Does it please the king to grant leave for my journey?” she asked, +looking up. + +“Yes, it pleases him.” + +“I am thankful,” she answered, “for I think that if I bide here much +longer, with ghosts and memories for company, I shall go mad,” and +she glanced at a spot near by, where the earth showed signs of recent +disturbance. + +“He gives leave,” Hokosa went on, taking no notice of her speech, “but +he suspects us. Listen----” and he told her of the talk that had passed +between himself and the king. + +“The white man has read you as he reads in his written books,” she +answered, with a little laugh. “Well, I said that he would be too clever +for you, did I not? It does not matter to me, for to-morrow I go upon my +journey, and you can settle it as you will.” + +“Ay!” answered Hokosa, grinding his teeth, “it is true that he has read +me; but this I promise you, that all books shall soon be closed to him. +Yet how is it to be done without suspicion or discovery? I know many +poisons, but all of them must be administered, and let him work never so +cunningly, he who gives a poison can be traced.” + +“Then cause some other to give it and let him bear the blame,” suggested +Noma languidly. + +Hokosa made no answer, but walking to the gate of the kraal, which was +open, he leaned against it lost in thought. As he stood thus he saw a +woman advancing towards him, who carried on her head a small basket of +fruit, and knew her for one of those whose business it was to wait upon +the Messenger in his huts, or rather in his house, for by now he had +built himself a small house, and near it a chapel. This woman saw Hokosa +also and looked at him sideways, as though she would like to stop and +speak to him, but feared to do so. + +“Good morrow to you, friend,” he said. “How goes it with your husband +and your house?” + +Now Hokosa knew well that this woman’s husband had taken a dislike to +her and driven her from his home, filling her place with one younger and +more attractive. At the question the woman’s lips began to tremble, and +her eyes swam with tears. + +“Ah! great doctor,” she said, “why do you ask me of my husband? Have you +not heard that he has driven me away and that another takes my place?” + +“Do I hear all the gossip of this town?” asked Hokosa, with a smile. +“But come in and tell me the story; perchance I may be able to help you, +for I have charms to compel the fancy of such faithless ones.” + +The woman looked round, and seeing that there was no one in sight, she +slipped swiftly through the gate of the kraal, which he closed behind +her. + +“Noma,” said Hokosa, “here is one who tells me that her husband has +deserted her, and who comes to seek my counsel. Bring her milk to +drink.” + +“There are some wives who would not find that so great an evil,” replied +Noma mockingly, as she rose to do his bidding. + +Hokosa winced at the sarcasm, and turning to his visitor, said:-- + +“Now tell me your tale; but say first, why are you so frightened?” + +“I am frightened, master,” she answered, “lest any should have seen +me enter here, for I have become a Christian, and the Christians are +forbidden to consult the witch-doctors, as we were wont to do. For my +case, it is----” + +“No need to set it out,” broke in Hokosa, waving his hand. “I see it +written on your face; your husband has put you away and loves another +woman, your own half-sister whom you brought up from a child.” + +“Ah! master, you have heard aright.” + +“I have not heard, I look upon you and I see. Fool, am I not a wizard? +Tell me----” and taking dust into his hand, he blew the grains this way +and that, regarding them curiously. “Yes, it is so. Last night you crept +to your husband’s hut--do you remember, a dog growled at you as you +passed the gate?--and there in front of the hut he sat with his new +wife. She saw you coming, but pretending not to see, she threw her arms +about his neck, kissing and fondling him before your eyes, till you +could bear it no longer, and revealed yourself, upbraiding them. Then +your rival taunted you and stirred up the man with bitter words, till at +length he took a stick and beat you from the door, and there is a mark +of it upon your shoulder.” + +“It is true, it is too true!” she groaned. + +“Yes, it is true. And now, what do you wish from me?” + +“Master, I wish a medicine to make my husband hate my rival and to draw +his heart back to me.” + +“That must be a strong medicine,” said Hokosa, “which will turn a man +from one who is young and beautiful to one who is past her youth and +ugly.” + +“I am as I am,” answered the poor woman, with a touch of natural +dignity, “but at least I have loved him and worked for him for fifteen +long years.” + +“And that is why he would now be rid of you, for who cumbers his kraal +with old cattle?” + +“And yet at times they are the best, Master. Wrinkles and smooth skin +seem strange upon one pillow,” she added, glancing at Noma, who came +from the hut carrying a bowl of milk in her hand. + +“If you seek counsel,” said Hokosa quickly, “why do you not go to the +white man, that Messenger in whom you believe, and ask him for a potion +to turn your husband’s heart?” + +“Master, I have been to him, and he is very good to me, for when I was +driven out he gave me work to do and food. But he told me that he had no +medicine for such cases, and that the Great Man in the sky alone could +soften the breast of my husband and cause my sister to cease from her +wickedness. Last night I went to see whether He would do it, and you +know what befell me there.” + +“That befell you which befalls all fools who put their trust in words +alone. What will you pay me, woman, if I give you the medicine which you +seek?” + +“Alas, master, I am poor. I have nothing to offer you, for when I would +not stay in my husband’s kraal to be a servant to his new wife, he took +the cow and the five goats that belonged to me, as, I being childless, +according to our ancient law he had the right to do.” + +“You are bold who come to ask a doctor to minister to you, bearing no +fee in your hand,” said Hokosa. “Yet, because I have pity on you, I will +be content with very little. Give me that basket of fruit, for my wife +has been sick and loves its taste.” + +“I cannot do that, Master,” answered the woman, “for it is sent by my +hand as a present to the Messenger, and he knows this and will eat of it +after he has made prayer to-day. Did I not give it to him, it would be +discovered that I had left it here with you.” + +“Then begone without your medicine,” said Hokosa, “for I need such +fruit.” + +The woman rose and said, looking at him wistfully:-- + +“Master, if you will be satisfied with other fruits of this same sort, I +know where I can get them for you.” + +“When will you get them?” + +“Now, within an hour. And till I return I will leave these in pledge +with you; but these and no other I must give to the Messenger, for he +has already seen them and might discover the difference; also I have +promised so to do.” + +“As you will,” said Hokosa. “If you are with the fruit within an hour, +the medicine will be ready for you, a medicine that shall not fail.” + + + +CHAPTER XIV + +THE EATING OF THE FRUIT + +The woman slipped away secretly. When she had gone Hokosa bade his wife +bring the basket of fruit into the hut. + +“It is best that the butcher should kill the ox himself,” she answered +meaningly. + +He carried in the basket and set it on the floor. + +“Why do you speak thus, Noma?” he asked. + +“Because I will have no hand in the matter, Hokosa. I have been the tool +of a wizard, and won little joy therefrom. The tool of a murderer I will +not be!” + +“If I kill, it is for the sake of both of us,” he said passionately. + +“It may be so, Hokosa, or for the sake of the people, or for the sake +of Heaven above--I do not know and do not care; but I say, do your own +killing, for I am sure that even less luck will hang to it than hangs to +your witchcraft.” + +“Of all women you are the most perverse!” he said, stamping his foot +upon the ground. + +“Thus you may say again before everything is done, husband; but if it +be so, why do you love me and tie me to you with your wizardry? Cut the +knot, and let me go my way while you go yours.” + +“Woman, I cannot; but still I bid you beware, for, strive as you will, +my path must be your path. Moreover, till I free you, you cannot lift +voice or hand against me.” + +Then, while she watched him curiously, Hokosa fetched his medicines and +took from them some powder fine as dust and two tiny crowquills. Placing +a fruit before him, he inserted one of these quills into its substance, +and filling the second with the powder, he shook its contents into it +and withdrew the tube. This process he repeated four times on each of +the fruits, replacing them one by one in the basket. So deftly did he +work upon them, that however closely they were scanned none could guess +that they had been tampered with. + +“Will it kill at once?” asked Noma. + +“No, indeed; but he who eats these fruits will be seized on the third +day with dysentery and fever, and these will cling to him till within +seven weeks--or if he is very strong, three months--he dies. This is the +best of poisons, for it works through nature and can be traced by none.” + +“Except, perchance, by that Spirit Whom the white man worships, and Who +also works through nature, as you learned, Hokosa, when He rolled the +lightning back upon your head, shattering your god and beating down your +company.” + +Then of a sudden terror seized the wizard, and springing to his feet, he +cursed his wife till she trembled before him. + +“Vile woman, and double-faced!” he said, “why do you push me forward +with one hand and with the other drag me back? Why do you whisper evil +counsel into one ear and into the other prophesy of misfortunes to come? +Had it not been for you, I should have let this business lie; I should +have taken my fate and been content. But day by day you have taunted me +with my fall and grieved over the greatness that you have lost, till +at length you have driven me to this. Why cannot you be all good or all +wicked, or at the least, through righteousness and sin, faithful to my +interest and your own?” + +“Because I hate you, Hokosa, and yet can strike you only through my +tongue and your mad love for me. I am fast in your power, but thus at +least I can make you feel something of my own pain. Hark! I hear that +woman at the gate. Will you give her back the basket, or will you not? +Whatever you may choose to do, do not say in after days that I urged you +to the deed.” + +“Truly you are great-hearted!” he answered, with cold contempt; “one for +whom I did well to enter into treachery and sin! So be it: having gone +so far upon it, come what may, I will not turn back from this journey. +Let in that fool!” + +Presently the woman stood before them, bearing with her another basket +of fruit. + +“These are what you seek, Master,” she said, “though I was forced to win +them by theft. Now give me my own and the medicine and let me go.” + +He gave her the basket, and with it, wrapped in a piece of kidskin, some +of the same powder with which he had doctored the fruits. + +“What shall I do with this?” she asked. + +“You must find means to sprinkle it upon your sister’s food, and +thereafter your husband shall come to hate even the sight of her.” + +“But will he come to love me again?” + +Hokosa shrugged his shoulders. + +“I know not,” he answered; “that is for you to see to. Yet this is sure, +that if a tree grows up before the house of a man, shutting it off from +the sunlight, when that tree is cut down the sun shines upon his house +again.” + +“It is nothing to the sun on what he shines,” said the woman. + +“If the saying does not please you, then forget it. I promise you this +and no more, that very soon the man shall cease to turn to your rival.” + +“The medicine will not harm her?” asked the woman doubtfully. “She has +worked me bitter wrong indeed, yet she is my sister, whom I nursed +when she was little, and I do not wish to do her hurt. If only he will +welcome me back and treat me kindly, I am willing even that she should +dwell on beneath my husband’s roof, bearing his children, for will they +not be of my own blood?” + +“Woman,” answered Hokosa impatiently, “you weary me with your talk. Did +I say that the charm would hurt her? I said that it would cause your +husband to hate the sight of her. Now begone, taking or leaving it, and +let me rest. If your mind is troubled, throw aside that medicine, and go +soothe it with such sights as you saw last night.” + +On hearing this the woman sprang up, hid away the poison in her hair, +and taking her basket of fruit, passed from the kraal as secretly as she +had entered it. + +“Why did you give her death-medicine?” asked Noma of Hokosa, as he stood +staring after her. “Have you a hate to satisfy against the husband or +the girl who is her rival?” + +“None,” he answered, “for they have never crossed my path. Oh, foolish +woman! cannot you read my plan?” + +“Not altogether, Husband.” + +“Listen then: this woman will give to her sister a medicine of which in +the end she must die. She may be discovered or she may not, but it is +certain that she will be suspected, seeing that the bitterness of the +quarrel between them is known. Also she will give to the Messenger +certain fruits, after eating of which he will be taken sick and in due +time die, of just such a disease as that which carries off the woman’s +rival. Now, if any think that he is poisoned, which I trust none will, +whom will they suppose to have poisoned him, though indeed they can +never prove the crime?” + +“The plan is clever,” said Noma with admiration, “but in it I see a +flaw. The woman will say that she had the drug from you, or, at the +least, will babble of her visit to you.” + +“Not so,” answered Hokosa, “for on this matter the greatest talker in +the world would keep silence. Firstly, she, being a Christian, dare not +own that she has visited a witch-doctor. Secondly, the fruit she brought +in payment was stolen, therefore she will say nothing of it. Thirdly, +to admit that she had medicine from me would be to admit her guilt, and +that she will scarcely do even under torture, which by the new law it is +not lawful to apply. Moreover, none saw her come here, and I should deny +her visit.” + +“The plan is very clever,” said Noma again. + +“It is very clever,” he repeated complacently; “never have I made a +better one. Now throw those fruits to the she goats that are in the +kraal, and burn the basket, while I go and talk to some in the Great +Place, telling them that I have returned from counting my cattle on the +mountain, whither I went after I had bowed the knee in the house of the +king.” + +***** + +Two hours later, Hokosa, having made a wide detour and talked to sundry +of his acquaintances about the condition of his cattle, might have been +seen walking slowly along the north side of the Great Place towards his +own kraal. His path lay past the chapel and the little house that Owen +had built to dwell in. This house was furnished with a broad verandah, +and upon it sat the Messenger himself, eating his evening meal. Hokosa +saw him, and a great desire entered his heart to learn whether or no +he had partaken of the poisoned fruit. Also it occurred to him that it +would be wise if, before the end came, he could contrive to divert all +possible suspicion from himself, by giving the impression that he was +now upon friendly terms with the great white teacher and not disinclined +even to become a convert to his doctrine. + +For a moment he hesitated, seeking an excuse. One soon suggested itself +to his ready mind. That very morning the king had told him not obscurely +that Owen had pleaded for his safety and saved him from being put upon +his trial on charges of witchcraft and murder. He would go to him, now +at once, playing the part of a grateful penitent, and the White Man’s +magic must be keen indeed if it availed to pierce the armour of his +practised craft. + +So Hokosa went up and squatted himself down native fashion among a +little group of converts who were waiting to see their teacher upon one +business or another. He was not more than ten paces from the verandah, +and sitting thus he saw a sight that interested him strangely. Having +eaten a little of a dish of roasted meat, Owen put out his hand and +took a fruit from a basket that the wizard knew well. At this moment he +looked up and recognised Hokosa. + +“Do you desire speech with me, Hokosa?” he asked in his gentle voice. +“If so, be pleased to come hither.” + +“Nay, Messenger,” answered Hokosa, “I desire speech with you indeed, but +it is ill to stand between a hungry man and his food.” + +“I care little for my food,” answered Owen; “at the least it can wait,” + and he put down the fruit. + +Then suddenly a feeling to which the wizard had been for many years a +stranger took possession of him--a feeling of compunction. That man was +about to partake of what would cause his death--of what he, Hokosa, had +prepared in order that it should cause his death. He was good, he was +kindly, none could allege a wrong deed against him; and, foolishness +though it might be, so was the doctrine that he taught. Why should he +kill him? It was true that never till that moment had he hesitated, by +fair means or foul, to remove an enemy or rival from his path. He +had been brought up in this teaching; it was part of the education of +wizards to be merciless, for they reigned by terror and evil craft. +Their magic lay chiefly in clairvoyance and powers of observation +developed to a pitch that was almost superhuman, and the best of +their weapons was poison in infinite variety, whereof the guild alone +understood the properties and preparation. Therefore there was nothing +strange, nothing unusual in this deed of devilish and cunning murder +that the sight of its doing should stir him thus, and yet it did stir +him. He was minded to stop the plot, to let things take their course. + +Some sense of the futility of all such strivings came home to him, and +as in a glass, for Hokosa was a man of imagination, he foresaw their +end. A little success, a little failure, it scarcely mattered which, and +then--that end. Within twenty years, or ten, or mayhap even one, what +would this present victory or defeat mean to him? Nothing so far as +he was concerned; that is, nothing so far as his life of to-day was +concerned. Yet, if he had another life, it might mean everything. There +was another life; he knew it, who had dragged back from its borders the +spirits of the dead, though what might be the state and occupations of +those dead he did not know. Yet he believed--why he could not tell--that +they were affected vitally by their acts and behaviour here; and his +intelligence warned him that good must always flow from good, and evil +from evil. To kill this man was evil, and of it only evil could come. + +What did he care whether Hafela ruled the nation or Nodwengo, and +whether it worshipped the God of the Christians or the god of Fire--who, +by the way, had proved himself so singularly inefficient in the hour of +trial. Now that he thought of it, he much preferred Nodwengo to Hafela, +for the one was a just man and the other a tyrant; and he himself was +more comfortable as a wealthy private person than he had been as a head +medicine-man and a chief of wizards. He would let things stand; he would +prevent the Messenger from eating of that fruit. A word could do it; he +had but to suggest that it was unripe or not wholesome at this season of +the year, and it would be cast aside. + +All these reflections, or their substance, passed through Hokosa’s +mind in a few instants of time, and already he was rising to go to +the verandah and translate their moral into acts, when another thought +occurred to him--How should he face Noma with this tale? He could give +up his own ambitions, but could he bear her mockery, as day by day +she taunted him with his faint-heartedness and reproached him with his +failure to regain greatness and to make her great? He forgot that he +might conceal the truth from her; or rather, he did not contemplate such +concealment, of which their relations were too peculiar and too intimate +to permit. She hated him, and he worshipped her with a half-inhuman +passion--a passion so unnatural, indeed, that it suggested the horrid +and insatiable longings of the damned--and yet their souls were naked +to each other. It was their fate that they could hide nothing each from +each--they were cursed with the awful necessity of candour. + +It would be impossible that he should keep from Noma anything that he +did or did not do; it would be still more impossible that she should +conceal from him even such imaginings and things as it is common for +women to hold secret. Her very bitterness, which it had been policy for +her to cloak or soften, would gush from her lips at the sight of him; +nor, in the depth of his rage and torment, could he, on the other hand, +control the ill-timed utterance of his continual and overmastering +passion. It came to this, then: he must go forward, and against his +better judgment, because he was afraid to go back, for the whip of +a woman’s tongue drove him on remorselessly. It was better that the +Messenger should die, and the land run red with blood, than that he +should be forced to endure this scourge. + +So with a sigh Hokosa sank back to the ground and watched while Owen ate +three of the poisoned fruits. After a pause, he took a fourth and bit +into it, but not seeming to find it to his taste, he threw it to a child +that was waiting by the verandah for any scraps which might be left over +from his meal. The child caught it, and devoured it eagerly. + +Then, smiling at the little boy’s delight, the Messenger called to +Hokosa to come up and speak with him. + + + +CHAPTER XV + +NOMA COMES TO HAFELA + +Hokosa advanced to the verandah and bowed to the white man with grave +dignity. + +“Be seated,” said Owen. “Will you not eat? though I have nothing to +offer you but these,” and he pushed the basket of fruits towards him, +adding, “The best of them, I fear, are already gone.” + +“I thank you, no, Messenger; such fruits are not always wholesome at +this season of the year. I have known them to breed dysentery.” + +“Indeed,” said Owen. “If so, I trust that I may escape. I have suffered +from that sickness, and I think that another bout of it would kill me. +In future I will avoid them. But what do you seek with me, Hokosa? Enter +and tell me,” and he led the way into a little sitting-room. + +“Messenger,” said the wizard, with deep humility, “I am a proud man; I +have been a great man, and it is no light thing to me to humble myself +before the face of my conqueror. Yet I am come to this. To-day when I +was in audience with the king, craving a small boon of his graciousness, +he spoke to me sharp and bitter words. He told me that he had been +minded to put me on trial for my life because of various misdoings which +are alleged against me in the past, but that you had pleaded for me +and that for this cause he spared me. I come to thank you for your +gentleness, Messenger, for I think that had I been in your place I +should have whispered otherwise in the ear of the king.” + +“Say no more of it, friend,” said Owen kindly, “We are all of us +sinners, and it is my place to push back your ancient sins, not to drag +them into the light of day and clamour for their punishment. It is true +I know that you plotted with the Prince Hafela to poison Umsuka the +King, for it was revealed to me. It chanced, however, that I was able +to recover Umsuka from his sickness, and Hafela is fled, so why should +I bring up the deed against you? It is true that you still practise +witchcraft, and that you hate and strive against the holy Faith which I +preach; but you were brought up to wizardry and have been the priest of +another creed, and these things plead for you. + +“Also, Hokosa, I can see the good and evil struggling in your soul, and +I pray and I believe that in the end the good will master the evil; +that you who have been pre-eminent in sin will come to be pre-eminent +in righteousness. Oh! be not stubborn, but listen with your ear, and +let your heart be softened. The gate stands open, and I am the guide +appointed to show you the way without reward or fee. Follow them ere it +be too late, that in time to come when my voice is stilled you also may +be able to direct the feet of wanderers into the paths of peace. It is +the hour of prayer; come with me, I beg of you, and listen to some few +words of the message of my lips, and let your spirit be nurtured with +them, and the Sun of Truth arise upon its darkness.” + +Hokosa heard, and before this simple eloquence his wisdom sank +confounded. More, his intelligence was stirred, and a desire came upon +him to investigate and examine the canons of a creed that could produce +such men as this. He made no answer, but waiting while Owen robed +himself, he followed him to the chapel. It was full of new-made +Christians who crowded even the doorways, but they gave place to him, +wondering. Then the service began--a short and simple service. First +Owen offered up some prayer for the welfare of the infant Church, for +the conversion of the unbelieving, for the safety of the king and the +happiness of the people. Then John, the Messenger’s first disciple, read +aloud from a manuscript a portion of the Scripture which his master had +translated. It was St. Paul’s exposition of the resurrection from the +dead, and the grandeur of its thoughts and language were by no means +lost upon Hokosa, who, savage and heathen though he might be, was also a +man of intellect. + +The reading over, Owen addressed the congregation, taking for his text, +“Thy sin shall find thee out.” Being now a master of the language, +he preached very well and earnestly, and indeed the subject was not +difficult to deal with in the presence of an audience many of whose +pasts had been stepped in iniquities of no common kind. As he talked of +judgment to come for the unrepentant, some of his hearers groaned and +even wept; and when, changing his note, he dwelt upon the blessed future +state of those who earned forgiveness, their faces were lighted up with +joy. + +But perhaps among all those gathered before him there were none more +deeply interested than Hokosa and one other, that woman to whom he +had sold the poison, and who, as it chanced, sat next to him. Hokosa, +watching her face as he was skilled to do, saw the thrusts of the +preacher go home, and grew sure that already in her jealous haste she +had found opportunity to sprinkle the medicine upon her rival’s food. +She believed it to be but a charm indeed, yet knowing that in using +such charms she had done wickedly, she trembled beneath the words of +denunciation, and rising at length, crept from the chapel. + +“Truly, her sin will find her out,” thought Hokosa to himself, and +then in a strange half-impersonal fashion he turned his thoughts to +the consideration of his own case. Would _his_ sin find him out? he +wondered. Before he could answer that question, it was necessary first +to determine whether or no he had committed a sin. The man before +him--that gentle and yet impassioned man--bore in his vitals the seed +of death which he, Hokosa, had planted there. Was it wrong to have done +this? It depended by which standard the deed was judged. According to +his own code, the code on which he had been educated and which hitherto +he had followed with exactness, it was not wrong. That code taught +the necessity of self-aggrandisement, or at least and at all costs the +necessity of self-preservation. This white preacher stood in his path; +he had humiliated him, Hokosa, and in the end, either of himself or +through his influences, it was probable that he would destroy him. +Therefore he must strike before in his own person he received a mortal +blow, and having no other means at his command, he struck through +treachery and poison. + +That was his law which for many generations had been followed and +respected by his class with the tacit assent of the nation. According to +this law, then, he had done no wrong. But now the victim by the altar, +who did not know that already he was bound upon the altar, preached a +new and a very different doctrine under which, were it to be believed, +he, Hokosa, was one of the worst of sinners. The matter, then, resolved +itself to this: which of these two rules of life was the right rule? +Which of them should a man follow to satisfy his conscience and to +secure his abiding welfare? Apart from the motives that swayed him, as a +mere matter of ethics, this problem interested Hokosa not a little, and +he went homewards determined to solve it if he might. That could be done +in one way only--by a close examination of both systems. The first he +knew well; he had practised it for nearly forty years. Of the second +he had but an inkling. Also, if he would learn more of it he must make +haste, seeing that its exponent in some short while would cease to be in +a position to set it out. + +“I trust that you will come again,” said Owen to Hokosa as they left the +chapel. + +“Yes, indeed, Messenger,” answered the wizard; “I will come every day, +and if you permit it, I will attend your private teachings also, for I +accept nothing without examination, and I greatly desire to study this +new doctrine of yours, root and flower and fruit.” + +***** + +On the morrow Noma started upon her journey. As the matrons who +accompanied her gave out with a somewhat suspicious persistency, its +ostensible object was to visit the Mount of Purification, and there by +fastings and solitude to purge herself of the sin of having given birth +to a stillborn child. For amongst savage peoples such an accident is +apt to be looked upon as little short of a crime, or, at the least, as +indicating that the woman concerned is the object of the indignation +of spirits who need to be appeased. To this Mount, Noma went, and there +performed the customary rites. + +“Little wonder,” she thought to herself, “that the spirits were angry +with her, seeing that yonder in the burying-ground of kings she had +dared to break in upon their rest.” + +From the Place of Purification she travelled on ten days’ journey with +her companions till they reached the mountain fastness where Hafela had +established himself. The town and its surroundings were of extraordinary +strength, and so well guarded that it was only after considerable +difficulty and delay that the women were admitted. Hearing of her +arrival and that she had words for him, Hafela sent for Noma at once, +receiving her by night and alone in his principal hut. She came and +stood before him, and he looked at her beauty with admiring eyes, for he +could not forget the woman whom the cunning of Hokosa had forced him to +put away. + +“Whence come you, pretty one?” he asked, “and wherefore come you? Are +you weary of your husband, that you fly back to me? If so, you are +welcome indeed; for know, Noma, that I still love you.” + +“Ay, Prince, I am weary of my husband sure enough; but I do not fly to +you, for he holds me fast to him with bonds that you cannot understand, +and fast to him while he lives I must remain.” + +“What hinders, Noma, that having got you here I should keep you here? +The cunning and magic of Hokosa may be great, but they will need to be +still greater to win you from my arms.” + +“This hinders, Prince, that you are playing for a higher stake than that +of a woman’s love, and if you deal thus by me and my husband, then of a +surety you will lose the game.” + +“What stake, Noma?” + +“The stake of the crown of the People of Fire.” + +“And why should I lose if I take you as a wife?” + +“Because Hokosa, seeing that I do not return and learning from his spies +why I do not return, will warn the king, and by many means bring all +your plans to nothing. Listen now to the words of Hokosa that he has +set between my lips to deliver to you”--and she repeated to him all the +message without fault or fail. + +“Say it again,” he said, and she obeyed. + +Then he answered:-- + +“Truly the skill of Hokosa is great, and well he knows how to set a +snare; but I think that if by his counsel I should springe the bird, he +will be too clever a man to keep upon the threshold of my throne. He +who sets one snare may set twain, and he who sits by the threshold may +desire to enter the house of kings wherein there is no space for two to +dwell.” + +“Is this the answer that I am to take back to Hokosa?” asked Noma. “It +will scarcely bind him to your cause, Prince, and I wonder that you dare +to speak it to me who am his wife.” + +“I dare to speak it to you, Noma, because, although you be his wife, all +wives do not love their lords; and I think that, perchance in days to +come, you would choose rather to hold the hand of a young king than that +of a witch-doctor sinking into eld. Thus shall you answer Hokosa: You +shall say to him that I have heard his words and that I find them very +good, and will walk along the path which he has made. Here before you I +swear by the oath that may not be broken--the sacred oath, calling down +ruin upon my head should I break one word of it--that if by his aid I +succeed in this great venture, I will pay him the price he asks. After +myself, the king, he shall be the greatest man among the people; he +shall be general of the armies; he shall be captain of the council +and head of the doctors, and to him shall be given half the cattle of +Nodwengo. Also, into his hand I will deliver all those who cling to this +faith of the Christians, and, if it pleases him, he shall offer them as +a sacrifice to his god. This I swear, and you, Noma, are witness to the +oath. Yet it may chance that after he, Hokosa, has gathered up all +this pomp and greatness, he himself shall be gathered up by Death, that +harvest-man whom soon or late will garner every ear;” and he looked at +her meaningly. + +“It may be so, Prince,” she answered. + +“It may be so,” he repeated, “and when----” + +“When it is so, then, Prince, we will talk together, but not till then. +Nay, touch me not, for were he to command me, Hokosa has this power over +me that I must show him all that you have done, keeping nothing back. +Let me go now to the place that is made ready for me, and afterwards you +shall tell me again and more fully the words that I must say to Hokosa +my husband.” + +***** + +On the morrow Hafela held a secret council of his great men, and the +next day an embassy departed to Nodwengo the king, taking to him that +message which Hokosa, through Noma his wife, had put into the lips +of the prince. Twenty days later the embassy returned saying that it +pleased the king to grant the prayer of his brother Hafela, and bringing +with it the tidings that the white man, Messenger, had fallen sick, and +it was thought that he would die. + +So in due course the women and children of the people of Hafela started +upon their journey towards the new land where it was given out that they +should live, and with them went Noma, purposing to leave them as they +drew near the gates of the Great Place of the king. A while after, +Hafela and his _impis_ followed with carriers bearing their fighting +shields in bundles, and having their stabbing spears rolled up in mats. + + + +CHAPTER XVI + +THE REPENTANCE OF HOKOSA + +Hokosa kept his promise. On the morrow of his first attendance there he +was again to be seen in the chapel, and after the service was over he +waited on Owen at his house and listened to his private teaching. Day +by day he appeared thus, till at length he became master of the whole +doctrine of Christianity, and discovered that that which at first had +struck him as childish and even monstrous, now presented itself to him +in a new and very different light. The conversion of Hokosa came upon +him through the gate of reason, not as is usual among savages--and some +who are not savage--by that of the emotions. Given the position of a +universe torn and groaning beneath the dual rule of Good and Evil, +two powers of well-nigh equal potency, he found no great difficulty in +accepting this tale of the self-sacrifice of the God of Good that He +might wring the race He loved out of the conquering grasp of the god of +Ill. There was a simple majesty about this scheme of redemption which +appealed to one side of his nature. Indeed, Hokosa felt that under +certain conditions and in a more limited fashion he would have been +capable of attempting as much himself. + +Once his reason was satisfied, the rest followed in a natural sequence. +Within three weeks from the hour of his first attendance at the chapel +Hokosa was at heart a Christian. + +He was a Christian, although as yet he did not confess it; but he was +also the most miserable man among the nation of the Sons of Fire. The +iniquities of his past life had become abominable to him; but he had +committed them in ignorance, and he understood that they were not beyond +forgiveness. Yet high above them all towered one colossal crime which, +as he believed, could never be pardoned to him in this world or the +next. He was the treacherous murderer of the Messenger of God; he was +in the very act of silencing the Voice that had proclaimed truth in the +dark places of his soul and the dull ears of his countrymen. + +The deed was done; no power on earth could save his victim. Within a +week from the day of eating that fatal fruit Owen began to sicken, then +the dysentery had seized him which slowly but surely was wasting out +his life. Yet he, the murderer, was helpless, for with this form of the +disease no medicine could cope. With agony in his heart, an agony that +was shared by thousands of the people, Hokosa watched the decrease of +the white man’s strength, and reckoned the days that would elapse before +the end. Having such sin as thus upon his soul, though Owen entreated +him earnestly, he would not permit himself to be baptised. Twice he +went near to consenting, but on each occasion an ominous and terrible +incident drove him from the door of mercy. + +Once, when the words “I will” were almost on his lips, a woman broke in +upon their conference bearing a dying boy in her arms. + +“Save him,” she implored, “save him, Messenger, for he is my only son!” + +Owen looked at him and shook his head. + +“How came he like this?” he asked. + +“I know not, Messenger, but he has been sick ever since he ate of a +certain fruit which you gave to him;” and she recalled to his mind +the incident of the throwing of a fruit to the child, which she had +witnessed. + +“I remember,” said Owen. “It is strange, but I also have been sick from +the day that I ate of those fruits; yes, and you, Hokosa, warned me +against them.” + +Then he blessed the boy and prayed over him till he died; but when +afterwards he looked round for Hokosa, it was to find that he had gone. + +Some eight days later, having to a certain extent recovered from this +shock, Hokosa went one morning to Owen’s house and talked to him. + +“Messenger,” he said, “is it necessary to baptism that I should confess +all my sins to you? If so, I can never be baptised, for there is +wickedness upon my hands which I am unable to tell into the ear of +living man.” + +Owen thought and answered:-- + +“It is necessary that you should repent all of your sins, and that +you should confess them to heaven; it is not necessary that you should +confess them to me, who am but a man like yourself.” + +“Then I will be baptised,” said Hokosa with a sigh of relief. + +At this moment, as it chanced, their interview was again interrupted, +for runners came from the king requesting the immediate presence of the +Messenger, if he were well enough to attend, upon a matter connected +with the trial of a woman for murder. Thinking that he might be of +service, Owen, leaning on the shoulder of Hokosa, for already he was too +weak to walk far, crept to the litter which was waiting for him, and was +borne to the place of judgment that was before the house of the king. +Hokosa followed, more from curiosity than for any other reason, for +he had heard of no murder being committed, and his old desire to be +acquainted with everything that passed was still strong on him. The +people made way for him, and he seated himself in the first line of +spectators immediately opposite to the king and three other captains +who were judges in the case. So soon as Owen had joined the judges, +the prisoner was brought before them, and to his secret horror Hokosa +recognised in her that woman to whom he had given the poison in exchange +for the basket of fruit. + +Now it seemed to Hokosa that his doom was on him, for she would +certainly confess that she had the drug from him. He thought of flight +only to reject the thought, for to fly would be to acknowledge himself +an accessory. No, he would brazen it out, for after all his word was as +good as hers. With the prisoner came an accuser, her husband, who seemed +sick, and he it was who opened the case against her. + +“This woman,” he said, “was my wife. I divorced her for barrenness, as +I have a right to do according to our ancient law, and I took another +woman to wife, her half-sister. This woman was jealous; she plagued me +continually, and insulted her sister, so that I was forced to drive her +away. After that she came to my house, and though they said nothing +of it at the time, she was seen by two servants of mine to sprinkle +something in the bowl wherein our food was cooking. Subsequently my +wife, this woman’s half-sister, was taken ill with dysentery. I also +was taken ill with dysentery, but I still live to tell this story before +you, O King, and your judges, though I know not for how long I live. My +wife died yesterday, and I buried her this morning. I accuse the woman +of having murdered her, either by witchcraft or by means of a medicine +which she sprinkled on the food, or by both. I have spoken.” + +“Have you anything to say?” asked the king of the prisoner. “Are you +guilty of the crime whereof this man who was your husband charges you, +or does he lie?” + +Then the woman answered in a low and broken voice:-- + +“I am guilty, King. Listen to my story:” and she told it all as she told +it to Hokosa. “I am guilty,” she added, “and may the Great Man in the +sky, of Whom the Messenger has taught us, forgive me. My sister’s blood +is upon my hands, and for aught I know the blood of my husband yonder +will also be on my hands. I seek no mercy; indeed, it is better that I +should die; but I would say this in self-defence, that I did not think +to kill my sister. I believed that I was giving to her a potion which +would cause her husband to hate her and no more.” + +Here she looked round and her eyes met those of Hokosa. + +“Who told you that this was so?” asked one of the judges. + +“A witch-doctor,” she answered, “from whom I bought the medicine in the +old days, long ago, when Umsuka was king.” + +Hokosa gasped. Why should this woman have spared him? + +No further question was asked of her, and the judges consulted together. +At length the king spoke. + +“Woman,” he said, “you are condemned to die. You will be taken to the +Doom Tree, and there be hanged. Out of those who are assembled to try +you, two, the Messenger and myself, have given their vote in favour of +mercy, but the majority think otherwise. They say that a law has been +passed against murder by means of witchcraft and secret medicine, and +that should we let you go free, the people will make a mock of that law. +So be it. Go in peace. To-morrow you must die, and may forgiveness await +you elsewhere.” + +“I ask nothing else,” said the woman. “It is best that I should die.” + +Then they led her away. As she passed Hokosa she turned and looked him +full in the eyes, till he dropped his head abashed. Next morning she was +executed, and he learned that her last words were: “Let it come to +the ears of him who sold me the poison, telling me that it was but +a harmless drug, that as I hope to be forgiven, so I forgive him, +believing that my silence may win for him time for repentance, before he +follows on the road I tread.” + +Now, when Hokosa heard these words he shut himself up in his house for +three days, giving out that he was sick. Nor would he go near to Owen, +being altogether without hope, and not believing that baptism or any +other rite could avail to purge such crimes as his. Truly his sin had +found him out, and the burden of it was intolerable. So intolerable did +it become, that at length he determined to be done with it. He could +live no more. He would die, and by his own hand, before he was called +upon to witness the death of the man whom he had murdered. To this end +he made his preparations. For Noma he left no message; for though his +heart still hungered after her, he knew well that she hated him and +would rejoice at his death. + +When all was ready he sat down to think a while, and as he thought, a +man entered his hut saying that the Messenger desired to see him. At +first he was minded not to go, then it occurred to him that it would be +well if he could die with a clean heart. Why should he not tell all to +the white man, and before he could be delivered up to justice take +that poison which he had prepared? It was impossible that he should be +forgiven, yet he desired that his victim should learn how deep was his +sorrow and repentance, before he proved it by preceding him to death. So +he rose and went. + +He found Owen in his house, lying in a rude chair and propped up by +pillows of bark. Now he was wasted almost to a shadow, and in the pale +pinched face his dark eyes, always large and spiritual, shone with +unnatural lustre, while his delicate hands were so thin that when he +held them up in blessing the light showed through them. + +“Welcome, friend,” he said. “Tell me, why have you deserted me of late? +Have you been ill?” + +“No, Messenger,” answered Hokosa, “that is, not in my body. I have been +sick at heart, and therefore I have not come.” + +“What, Hokosa, do your doubts still torment you? I thought that my +prayers had been heard, and that power had been given me to set them at +rest for ever. Man, let me hear the trouble, and swiftly, for cannot you +who are a doctor see that I shall not be here for long to talk with you? +My days are numbered, Hokosa, and my work is almost done.” + +“I know it,” answered Hokosa. “And, Messenger, _my_ days are also +numbered.” + +“How is this?” asked Owen, “seeing that you are well and strong. Does an +enemy put you in danger of your life?” + +“Yes, Messenger, and I myself am that enemy; for to-day I, who am no +longer fit to live, must die by my own hand. Nay, listen and you will +say that I do well, for before I go I would tell you all. Messenger, you +are doomed, are you not? Well, it was I who doomed you. That fruit which +you ate a while ago was poisoned, and by my hand, for I am a master of +such arts. From the beginning I hated you, as well I might, for had you +not worsted me and torn power from my grasp, and placed the people and +the king under the rule of another God? Therefore, when all else failed, +I determined to murder you, and I did the deed by means of that woman +who not long ago was hung for the killing of her sister, though in truth +she was innocent.” And he told him what had passed between himself and +the woman, and told him also of the plot which he had hatched to kill +Nodwengo and the Christians, and to set Hafela on the throne. + +“She was innocent,” he went on, “but I am guilty. How guilty you and I +know alone. Do you remember that day when you ate the fruit, how +after it I accompanied you to the church yonder and listened to your +preaching? ‘Your sin shall find you out,’ you said, and of a surety mine +has found me out. For, Messenger, it came about that in listening to +you then and afterwards, I grew to love you and to believe the words you +taught, and therefore am I of all men the most miserable, and therefore +must I, who have been great and the councillor of kings, perish +miserably by the death of a dog. + +“Now curse me, and let me go.” + + + +CHAPTER XVII + +THE LOOSING OF NOMA + +When Owen heard that it was Hokosa who had poisoned him, he groaned and +hid his face in his hands, and thus he remained till the evil tale was +finished. Now he lifted his head and spoke, but not to Hokosa. + +“O God,” he said, “I thank Thee that at the cost of my poor life Thou +hast been pleased to lead this sinner towards the Gate of Righteousness, +and to save alive those whom Thou hast sent me to gather to Thy Fold.” + +Then he looked at Hokosa and said:-- + +“Unhappy man, is not your cup full enough of crime, and have you not +sufficiently tempted the mercy of Heaven, that you would add to all your +evil deeds that of self-murder?” + +“It is better to die to-day by my own hand,” answered Hokosa, “than +to-morrow among the mockery of the people to fall a victim to your +vengeance, Messenger.” + +“Vengeance! Did I speak to you of vengeance? Who am I that I should take +vengeance upon one who has repented? Hokosa, freely do I forgive you +all, even as in some few days I hope to be forgiven. Freely and fully +from my heart do I forgive you, nor shall my lips tell one word of the +sin that you have worked against me.” + +Now, when Hokosa heard those words, for a moment he stared stupefied; +then he fell upon his knees before Owen, and bowing his head till it +touched the teacher’s feet, he burst into bitter weeping. + +“Rise and hearken,” said Owen gently. “Weep not because I have shown +kindness to you, for that is my duty and no more, but for your sins in +your own heart weep now and ever. Yet for your comfort I tell you that +if you do this, of a surety they shall be forgiven to you. _Hokosa, you +have indeed lost that which you loved, and henceforth you must follow +after that which you did not desire. In the very grave of error you have +found truth, and from the depths of sin you shall pluck righteousness. +Ay, that Cross which you deemed accursed shall lift you up on high, for +by it you shall be saved._” + +Hokosa heard and shivered. + +“Who set those words between your lips, Messenger?” he whispered. + +“Who set them, Hokosa? Nay, I know not--or rather, I know well. He set +them Who teaches us to speak all things that are good.” + +“It must be so, indeed,” replied Hokosa. “Yet I have heard them before; +I have heard them from the lips of the dead, and with them went this +command: that when they fell upon my ears again I should ‘take them for +a sign, and let my heart be turned.’” + +“Tell me that tale,” said Owen. + +So he told him, and this time it was the white man who trembled. + +“Horrible has been your witchcraft, O Son of Darkness!” said Owen, when +he had finished; “yet it would seem that it was permitted to you to find +truth in the pit of sorcery. Obey, obey, and let your heart be turned. +The dead told you that you should be set high above the nation and its +king, and that saying I cannot read, though it may be fulfilled in some +fashion of which to-day you do not think. At the least, the other saying +is true, that in the end comes judgment, and that there shall the +sin and the atonement strive together; therefore for judgment prepare +yourself. And now depart, for I must talk with the king as to this +matter of the onslaught of Hafela.” + +“Then, that will be the signal for my death, for what king can forgive +one who has plotted such treachery against him?” said Hokosa. + +“Fear not,” answered Owen, “I will soften his heart. Go you into the +church and pray, for there you shall be less tempted; but before you go, +swear to me that you will work no evil on yourself.” + +“I swear it, Messenger, since now I desire to live, if only for awhile, +seeing that death shuts every door.” + +Then he went to the church and waited there. An hour later he was +summoned, and found the king seated with Owen. + +“Man,” said Nodwengo, “I am told by the Messenger here that you have +knowledge of a plot which my brother the Prince Hafela has made to fall +treacherously upon me and put me and my people to the spear. How you +come to be acquainted with the plot, and what part you have played +in it, I will not now inquire, for so much have I promised to the +Messenger. Yet I warn you it will be well that you should tell me all +you know, and that should you lie to me or attempt to deceive me, then +you shall surely die.” + +“King, hear all the truth,” answered Hokosa in a voice of desperate +calm. “I have knowledge of the plot, for it was I who wove it; but +whether or not Hafela will carry it out altogether I cannot say, for +as yet no word has reached me from him. King, this was the plan that I +made.” And he told him everything. + +“It is fortunate for you, Hokosa,” said Nodwengo grimly when he had +finished, “that I gave my word to the Messenger that no harm should come +to you, seeing that you have repented and confessed. This is certain, +that Hafela has listened to your evil counsels, for I gave my consent to +his flight from this land with all his people, and already his women +and children have crossed the mountain path in thousands. Well, this +I swear, that their feet shall tread it no more, for where they are +thither he shall go to join them, should he chance to live to do so. +Hokosa, begone, and know that day and night you will be watched. Should +you so much as dare to approach one of the gates of the Great Place, +that moment you shall die.” + +“Have no fear, O King,” said Hokosa humbly, “for I have emptied all my +heart before you. The past is the past, and cannot be recalled. For the +future, while it pleases you to spare me, I am the most loyal of your +servants.” + +“Can a man empty a spring with a pitcher?” asked the king +contemptuously. “By to-morrow this heart of yours may be full again +with the blackest treachery, O master of sin and lies. Many months ago I +spared you at the prayer of the Messenger; and now at his prayer I spare +you again, yet in doing so I think that I am foolish.” + +“Nay, I will answer for him,” broke in Owen. “Let him stay here with me, +and set your guard without my gates.” + +“How do I know that he will not murder you, friend?” asked the king. +“This man is a snake whom few can nurse with safety.” + +“He will not murder me,” said Owen smiling, “because his heart is turned +from evil to good; also, there is little need to murder a dying man.” + +“Nay, speak not so,” said the king hastily; “and as for this man, be it +as you will. Come, I must take counsel with my captains, for our danger +is near and great.” + +So it came about that Hokosa stayed in the house of Owen. + +On the morrow the Great Place was full of the bustle of preparation, and +by dawn of the following day an _impi_ of some seventeen thousand spears +had started to ambush Hafela and his force in a certain wooded defile +through which he must pass on his way to the mountain pass where his +women and children were gathered. The army was not large, at least in +the eyes of the People of Fire who, before the death of Umsuka and the +break up of the nation, counted their warriors by tens of thousands. +But after those events the most of the regiments had deserted to Hafela, +leaving to Nodwengo not more than two-and-twenty thousand spears upon +which he could rely. Of these he kept less than a third to defend the +Great Place against possible attacks, and all the rest he sent to fall +upon Hafela far away, hoping there to make an end of him once and for +all. This counsel the king took against the better judgment of many of +his captains, and as the issue proved, it was mistaken. + +When Owen told Hokosa of it, that old general shrugged his shoulders. + +“The king would have done better to keep his regiments at home,” he +said, “and fight it out with Hafela here, where he is well prepared. +Yonder the country is very wide, and broken, and it may well chance that +the _impi_ will miss that of Hafela, and then how can the king defend +this place with a handful, should the prince burst upon him at the head +of forty thousand men? But who am I that I should give counsel for which +none seek?” + +“As God wills, so shall it befall,” answered Owen wearily; “but oh! the +thought of all this bloodshed breaks my heart. I trust that its beatings +may be stilled before my eyes behold the evil hour.” + +On the evening of that day Hokosa was baptised. The ceremony took place, +not in the church, for Owen was too weak to go there, but in the +largest room of his house and before some few witnesses chosen from the +congregation. Even as he was being signed with the sign of the cross, +a strange and familiar attraction caused the convert to look up, and +behold, before him, watching all with mocking eyes, stood Noma his wife. +At length the rite was finished, and the little audience melted away, +all save Noma, who stood silent and beautiful as a statue, the light of +mockery still gleaming in her eyes. Then she spoke, saying:-- + +“I greet you, Husband. I have returned from doing your business afar, +and if this foolishness is finished, and the white man can spare you, I +would talk with you alone.” + +“I greet you, Wife,” answered Hokosa. “Say out your say, for none are +present save us three, and from the Messenger here I have no secrets.” + +“What, Husband, none? Do you ever talk to him of certain fruit that you +ripened in a garden yonder?” + +“From the Messenger I have no secrets,” repeated Hokosa in a heavy +voice. + +“Then his heart must be full of them indeed, and it is little wonder +that he seems sick,” replied Noma, gibing. “Tell me, Hokosa, is it true +that you have become a Christian, or would you but fool the white man +and his following?” + +“It is true.” + +At the words her graceful shape was shaken with a little gust of silent +laughter. + +“The wizard has turned saint,” she said. “Well, then, what of the +wizard’s wife?” + +“You were my wife before I became Christian; if the Messenger permits +it, you can still abide with me.” + +“If the Messenger permits it! So you have come to this, Hokosa, that you +must ask the leave of another man as to whether or no you should keep +your own wife! There is no other thing that I could not have thought of +you, but this I would never have believed had I not heard it from your +lips. Say now, do you still love me, Hokosa?” + +“You know well that I love you, now and always,” he answered, in a voice +that sounded like a groan; “as you know that for love of you I have done +many sins from which otherwise I should have turned aside.” + +“Grieve not over them, Hokosa; after all, in such a count as yours they +will make but little show. Well, if you love me, I hate you, though +through your witchcraft your will yet has the mastery of mine. I demand +of you now that you should loose that bond, for I do not desire to +become a Christian; and surely, O most good and holy man, having one +wife already, it will not please you henceforth to live in sin with a +heathen woman.” + +Now Hokosa turned to Owen:-- + +“In the old days,” he said, “I could have answered her; but now I am +fallen; or raised up--at the least I am changed and cannot. O prophet of +Heaven, tell me what I shall do.” + +“Sever the bond that you have upon her and let her go,” answered Owen. +“This love of yours is unnatural, unholy and born of witchcraft; have +done with it, or if you cannot, at the least deny it, for such a woman, +a woman who hates you, can work you no good. Moreover, since she is a +second wife, you being a Christian, are bound to free her should she so +desire.” + +“She can work me no good, Messenger, that I know; but I know also that +while she struggles in the net of my will she can work me no evil. If I +loose the net and the fish swims free, it may be otherwise.” + +“Loose it,” answered Owen, “and leave the rest to Providence. +Henceforth, Hokosa, do right, and take no thought for the morrow, for +the morrow is with God, and what He decrees, that shall befall.” + +“I hear you,” said Hokosa, “and I obey.” For a while he rocked himself +to and fro, staring at the ground, then he lifted his head and spoke:-- + +“Woman,” he said, “the knot is untied and the spell is broken. Begone, +for I release you and I divorce you. Flesh of my flesh have you been, +and soul of my soul, for in the web of sorceries are we knit together. +Yet be warned and presume not too far, for remember that which I have +laid down I can take up, and that should I choose to command, you must +still obey. Farewell, you are free.” + +Noma heard, and with a sigh of ecstasy she sprang into the air as a +slave might do from whom the fetters have been struck off. + +“Ay,” she cried, “I am free! I feel it in my blood, I who have lain in +bondage, and the voice of freedom speaks in my heart and the breath of +freedom blows in my nostrils. I am free from you, O dark and accursed +man; but herein lies my triumph and revenge--_you_ are not free from me. +In obedience to that white fool whom you have murdered, you have loosed +me; but you I will not loose and could not if I would. Listen now, +Hokosa: you love me, do you not?--next to this new creed of yours, I am +most of all to you. Well, since you have divorced me, I will tell you, I +go straight to another man. Now, look your last on me; for you love me, +do you not?” and she slipped the mantle from her shoulders and except +for her girdle stood before him naked, and smiled. + +“Well,” she went on, resuming her robe, “the last words of those we love +are always dear to us; therefore, Hokosa, you who were my husband, I +leave mine with you. You are a coward and a traitor, and your doom shall +be that of a coward and a traitor. For my sake you betrayed Umsuka, your +king and benefactor; for your own sake you betrayed Nodwengo, who spared +you; and now, for the sake of your miserable soul, you have betrayed +Hafela to Nodwengo. Nay, I know the tale, do not answer me, but the end +of it--ah! that is yet to learn. Lie there, snake, and lick the hand +that you have bitten, but I, the bird whom you have loosed, I fly +afar--taking your heart with me!” and suddenly she turned and was gone. + +Presently Hokosa spoke in a thick voice:-- + +“Messenger,” he said, “this cross that you have given me to bear is +heavy indeed.” + +“Yes, Hokosa,” answered Owen, “for to it your sins are nailed.” + + + +CHAPTER XVIII + +THE PASSING OF OWEN + +Once she was outside of Owen’s house, Noma did not tarry. First she +returned to Hokosa’s kraal, where she had already learnt from his head +wife, Zinti, and others the news of his betrayal of the plot of Hafela, +of his conversion to the faith of the Christians, and of the march +of the _impi_ to ambush the prince. Here she took a little spear, and +rolling up in a skin blanket as much dried meat as she could carry, +she slipped unnoticed from the kraal. Her object was to escape from the +Great Place, but this she did not try to do by any of the gates, knowing +them to be guarded. Some months ago, before she started on her embassy, +she had noted a weak spot in the fence, where dogs had torn a hole +through which they passed out to hunt at night. To this spot she made +her way under cover of the darkness--for though she still greatly feared +to be alone at night, her pressing need conquered her fears--and found +that the hole was yet there, for a tall weed growing in its mouth had +caused it to be overlooked by those whose duty it was to mend the fence. +With her assegai she widened it a little, then drew her lithe shape +through it, and lying hidden till the guard had passed, climbed the two +stone walls beyond. Once she was free of the town, she set her course by +the stars and started forward at a steady run. + +“If my strength holds I shall yet be in time to warn him,” she muttered +to herself. “Ah! friend Hokosa, this new madness of yours has blunted +your wits that once were sharp enough. You have set me free, and now you +shall learn how I can use my freedom. Not for nothing have I been your +pupil, Hokosa the fox.” + +Before the dawn broke Noma was thirty miles from the Great Place, and +before the next dawn she was a hundred. At sunset on that second day she +stood among mountains. To her right stretched a great defile, a rugged +place of rocks and bush, wherein she knew that the regiments of the king +were hid in ambush. Perchance she was too late, perchance the _impi_ of +Hafela had already passed to its doom in yonder gorge. Swiftly she ran +forward on to the trail which led to the gorge, to find that it had been +trodden by many feet and recently. Moving to and fro she searched the +spoor with her eyes, then rose with a sigh of joy. It was old, and +marked the passage of the great company of women and children and their +thousands of cattle which, in execution of the plot, had travelled this +path some days before. Either the _impi_ had not yet arrived, or it had +gone by some other road. Weary as she was, Noma followed the old spoor +backwards. A mile or more away it crossed the crest of a hog-backed +mountain, from whose summit she searched the plain beyond, and not in +vain, for there far beneath her twinkled the watch-fires of the army of +Hafela. + +Three hours later a woman, footsore and utterly exhausted, staggered +into the camp, and waving aside the spears that were lifted to stab her, +demanded to be led to the prince. Presently she was there. + +“Who is this woman?” asked the great warrior; for, haggard as she was +with travel, exhaustion, and the terror of her haunted loneliness, he +did not know her in the uncertain firelight. + +“Hafela,” she said, “I am Noma who was the wife of Hokosa, and for whole +nights and days I have journeyed as no woman ever journeyed before, to +tell you of the treachery of Hokosa and to save you from your doom.” + +“What treachery and what doom?” asked the prince. + +“Before I answer you that question, Hafela, you must pay me the price of +my news.” + +“Let me hear the price, Noma.” + +“It is this, Prince: First, the head of Hokosa, who has divorced me, +when you have caught him.” + +“That I promise readily. What more?” + +“Secondly, the place of your chief wife to-day; and a week hence, when +I shall have made you king, the name and state of Queen of the People of +Fire with all that hangs thereto.” + +“You are ambitious, woman, and know well how to drive a bargain. Well, +if you can ask, I can give, for I have ever loved you, and your mind is +great as your body is beautiful. If through your help I should become +King of the People of Fire, you shall be their Queen, I swear it by the +spirits of my fathers and by my own head. And now--your tidings.” + +“These are they, Hafela. Hokosa has turned Christian and betrayed the +plot to Nodwengo; and the great gorge yonder but three hours march away +is ambushed. To-morrow you and your people would have been cut off there +had I not run so fast and far to warn you, after which the _impis_ +of Nodwengo were commanded to follow your women and cattle over the +mountain pass and capture them.” + +“This is news indeed,” said the prince. “Say now, how many regiments are +hidden in the gorge?” + +“Eight.” + +“Well, I have fourteen; so, being warned, there is little to fear. I +will catch these rats in their own hole.” + +“I have a better plan,” said Noma; “it is this: leave six regiments +posted upon the brow of yonder hill and let them stay there. Then when +the generals of Nodwengo see that they do not enter the gorge, they will +believe that the ambush is discovered, and, after waiting one day or +perhaps two, will move out to give battle, thinking that before them is +all your strength. But command your regiments to run and not to fight, +drawing the army of Nodwengo after them. Meanwhile, yes, this very +night, you yourself with all the men that are left to you must march +upon the Great Place, which, though it be strong, can be stormed, for it +is defended by less than five thousand soldiers. There, having taken it, +you shall slay Nodwengo, proclaiming yourself king, and afterwards, by +the help of the _impi_ that you leave here which will march onward to +your succour, you can deal with yonder army.” + +“A great scheme truly,” said Hafela in admiration; “but how do I know +whether all this tale is true, or whether you do but set a snare for +me?” + +“Bid scouts go out and creep into yonder gully,” answered Noma, “and you +will see whether or no I have spoken falsely. For the rest, I am in your +hands, and if I lie you can take my life in payment.” + +“If I march upon the Great Place, it must be at midnight when none see +me go,” said Hafela, “and what will you do then, Noma, who are too weary +to travel again so soon?” + +“I will be borne in a litter till my strength comes back to me,” she +answered. “And now give me to eat and let me rest while I may.” + +***** + +Five hours later, Hafela with the most of his army, a force of something +over twenty thousand men, was journeying swiftly but by a circuitous +route towards the Great Place of the king. On the crest of the hill +facing the gorge, as Noma had suggested, he left six regiments with +instructions to fly before Nodwengo’s generals, and when they had led +them far enough, to follow him as swiftly as they were able. These +orders, or rather the first part of them, they carried out, for as it +chanced after two days’ flight, the king’s soldiers got behind them by +a night march, and falling on them at dawn, killed half of them and +dispersed the rest. Then it was that Nodwengo’s generals learned for +the first time that they were following one wing of Hafela’s army only, +while the main body was striking at the heart of the kingdom, and turned +their faces homewards in fear and haste. + +***** + +On the morning after the flight of Noma, Owen passed into the last stage +of his sickness, and it became evident, both to himself and to those +who watched him, that at the most he could not live for more than a few +days. For his part, he accepted his doom joyfully, spending the time +which was left to him in writing letters that were to be forwarded to +England whenever an opportunity should arise. Also he set down on paper +a statement of the principal events of his strange mission, and other +information for the guidance of his white successors, who by now should +be drawing near to the land of the Amasuka. In the intervals of these +last labours, from time to time he summoned the king and the wisest and +trustiest of them whom he had baptised to his bedside, teaching them +what they should do when he was gone, and exhorting them to cling to the +Faith. + +On the afternoon of the fourth day from that of the baptism of Hokosa he +fell into a quiet sleep, from which he did not wake till sundown. + +“Am I still here?” he asked wondering, of John and Hokosa who watched at +his bedside. “From my dreams I thought that it was otherwise. John, send +a messenger to the king and ask of him to assemble the people, all who +care to come, in the open place before my house. I am about to die, and +first I would speak with them.” + +John went weeping upon his errand, leaving Owen and Hokosa alone. + +“Tell me know what shall I do?” said Hokosa in a voice of despair, +“seeing that it is I and no other who have brought this death upon you.” + +“Fret not, my brother,” answered Owen, “for this and other things you +did in the days of your blindness, and it was permitted that you should +do them to an end. Kneel down now, that I may absolve you from your sins +before I pass away; for I tell you, Hokosa, I believe that ere many days +are over you must walk on the same path which I travel to-night.” + +“Is it so?” Hokosa answered. “Well, I am glad, for I have no longer any +lust of life.” + +Then he knelt down and received the absolution. + +Now John returned and Nodwengo with him, who told him that the people +were gathering in hundreds according to his wish. + +“Then clothe me in my robes and let us go forth,” he said, “for I would +speak my last words in the ears of men.” + +So they put the surplice and hood upon his wasted form and went out, +John preceding him holding on high the ivory crucifix, while the king +and Hokosa supported him, one on either side. + +Without his gate stood a low wooden platform, whence at times Owen had +been accustomed to address any congregation larger than the church would +contain. On this platform he took his seat. The moon was bright above +him, and by it he could see that already his audience numbered some +thousands of men, women and children. The news had spread that the +wonderful white man, Messenger, wished to take his farewell of the +nation, though even now many did not understand that he was dying, but +imagined that he was about to leave the country, or, for aught they +knew, to vanish from their sight into Heaven. For a moment Owen looked +at the sea of dusky faces, then in the midst of an intense stillness, he +spoke in a voice low indeed but clear and steady:-- + +“My children,” he said, “hear my last words to you. More than three +years ago, in a far, far land and upon such a night as this, a Voice +spoke to me from above commanding me to seek you out, to turn you from +your idolatry and to lighten your darkness. I listened to the Voice, and +hither I journeyed across sea and land, though how this thing might be +done I could not guess. But to Him Who sent me all things are possible, +and while yet I lingered upon the threshold of your country, in a dream +were revealed to me events that were to come. So I appeared before you +boldly, and knowing that he had been poisoned and that I could cure +him, I drew back your king from the mouth of death, and you said to +yourselves: ‘Behold a wizard indeed! Let us hear him.’ Then I gave +battle to your sorcerers yonder upon the plain, and from the foot of the +Cross I teach, the lightnings were rolled back upon them and they were +not. Look now, their chief stands at my side, among my disciples one +of the foremost and most faithful. Afterwards troubles arose: your king +died a Christian, and many of the people fell away; but still a remnant +remained, and he who became king was converted to the truth. Now I +have sown the seed, and the corn is ripe before my eyes, but it is not +permitted that I should reap the harvest. My work is ended, my task is +done, and I, the Messenger, return to make report to Him Who sent the +message. + +“Hear me yet a little while, for soon shall my voice be silent. ‘I come +not to bring peace, but a sword,’--so said the Master Whom I preach, and +so say I, the most unworthy of His servants. Salvation cannot be bought +at a little price; it must be paid for by the blood and griefs of +men, and in blood and griefs must you pay, O my children. Through much +tribulation must you also enter the kingdom of God. Even now the heathen +is at your gates, and many of you shall perish on his spears, but I tell +you that he shall not conquer. Be faithful, cling to the Cross, and do +not dare to doubt your Lord, for He will be your Captain and you shall +be His people. Cleave to your king, for he is good; and in the day of +trial listen to the counsel of this Hokosa who once was the first of +evil-doers, for with him goes my spirit, and he is my son in the spirit. + +“My children, fare you well! Forget me not, for I have loved you; or if +you will, forget me, but remember my teaching and hearken to those who +shall tread upon the path I made. The peace of God be with you, the +blessing of God be upon you, and the salvation of God await you, as it +awaits me to-night! Friends, lead me hence to die.” + +They turned to him, but before their hands touched him Thomas Owen fell +forward upon the breast of Hokosa and lay there a while. Then suddenly, +for the last time, he lifted himself and cried aloud:-- + +“I have fought a good fight! I have finished my course! I have kept the +faith! Henceforth there is laid up for me a crown of righteousness . . . +and not to me only, but to all those who love His appearing.” + +Then his head fell back, his dark eyes closed, and the Messenger was +dead. + +Hokosa, the man who had murdered him, having lifted him up to show him +to the people, amidst a sound of mighty weeping, took the body in his +arms and bore it thence to make it ready for burial. + + + +CHAPTER XIX + +THE FALL OF THE GREAT PLACE + +On the morrow at sundown all that remained of Thomas Owen was laid to +rest before the altar of the little church, Nodwengo the king and Hokosa +lowering him into the grave, while John, his first disciple, read over +him the burial service of the Christians, which it had been one of the +dead man’s last labours to translate into the language of the Amasuka. + +Before the ceremony was finished, a soldier, carrying a spear in his +hand, pushed his way through the dense and weeping crowd, and having +saluted, whispered something into the ear of the king. Nodwengo started, +and, with a last look of farewell at the face of his friend, left the +chapel, accompanied by some of his generals who were present, muttering +to Hokosa that he was to follow when all was done. Accordingly, some +few minutes later, he went and was admitted into the Council Hut, +where captains and messengers were to be seen arriving and departing +continuously. + +“Hokosa,” said the king, “you have dealt treacherously with me in the +past, but I believe now that your heart is true; at the least I follow +the commands of our dead master and trust you. Listen: the outposts have +sighted an _impi_ of many regiments advancing towards the Great Place, +though whether or no it be my own _impi_ returning victorious from the +war with my brother, I cannot say. There is this against it, however, +that a messenger has but just arrived reporting that the generals have +perceived the host of Hafela encamped upon a ridge over against the +gorge where they awaited him. If that be so, they can scarcely have +given him battle, for the messenger is swift of foot and has travelled +night and day. Yet how can this be the _impi_ of Hafela, who, say the +generals, is encamped upon the ridge?” + +“He may have left the ridge, King, having been warned of the ambush.” + +“It cannot be, for when the runner started his fires burned there and +his soldiers were gathered round them.” + +“Then perhaps his captains sit upon the ridge with some portion of his +strength to deceive those who await him in the gorge; while, knowing +that here men are few, he himself swoops down on you with the main body +of his _impi_.” + +“At least we shall learn presently,” answered the king; “but if it be +as I fear and we are outwitted, what is there that we can do against so +many?” + +Now one of the captains proposed that they should stay where they were +and hold the place. + +“It is too large,” answered the king, “they will burst the fences and +break our line.” + +Another suggested that they should fly and, avoiding the regiments of +Hafela in the darkness of the night, should travel swiftly in search of +the main army that had been sent to lie in ambush. + +“What,” said Nodwengo, “leaving the aged and the women and children to +perish, for how can we take such a multitude? No, I will have none of +this plan.” + +Then Hokosa spoke. “King,” he said, “listen to my counsel: Command now +that all the women and the old men, taking with them such cattle and +food as are in the town, depart at once into the Valley of Death and +collect in the open space that lies beyond the Tree of Doom, near the +spring of water that is there. The valley is narrow and the cliffs are +steep, and it may chance that by the help of Heaven we shall be able to +hold it till the army returns to relieve us, to seek which messengers +must be sent at once with these tidings.” + +“The plan is good,” said the king, though none had thought of it; “but +so we shall lose the town.” + +“Towns can be rebuilt,” answered Hokosa, “but who may restore the lives +of men?” + +As the words left his lips, a runner burst into the council, crying: +“King, the _impi_ is that of Hafela, and the prince heads it in person. +Already his outposts rest upon the Plain of Fire.” + +Then Nodwengo rose and issued his orders, commanding that all the +ineffective population of the town, together with such food and cattle +as could be gathered, should retreat at once into the Valley of Death. +By this time the four or five thousand soldiers who were left in the +Great Place had been paraded on the open ground in front of the king’s +house, where they stood, still and silent, in the moonlight. Nodwengo +and the captains went out to them, and as they saw him come they lifted +their spears like one man, giving him the royal salute of “King!” He +held up his hand and addressed them. + +“Soldiers,” he said, “we have been outwitted. My _impi_ is afar, and +that of Hafela is at our gates. Yonder in the valley, though we be +few, we can defend ourselves till succour reaches us, which already +messengers have gone out to seek. But first we must give time for the +women and children, the sick and the aged, to withdraw with food and +cattle; and this we can do in one way only, by keeping Hafela at bay +till they have passed the archway, all of them. Now, soldiers, for the +sake of your own lives, of your honour and of those you love, swear to +me, in the holy Name which we have been taught to worship, that you will +fight out this great fight without fear or faltering.” + +“We swear it in the holy Name, and by your head, King,” roared the +regiments. + +“Then victory is already ours,” answered Nodwengo. “Follow me, Children +of Fire!” and shaking his great spear, he led the way towards that +portion of the outer fence upon which Hafela was advancing. + +By now the town behind them was a scene of almost indescribable tumult +and confusion, for the companies detailed to the task were clearing the +numberless huts of their occupants, and collecting women, children and +oxen in thousands, preparatory to driving them into the defile. Panic +had seized many of these poor creatures, who, in imagination, already +saw themselves impaled upon the cruel spears of Hafela’s troops, and +indeed in not a few instances believed those who were urging them +forward to be the enemy. Women shrieked and wrung their hands, children +wailed piteously, oxen lowed, and the infirm and aged vented their +grief in groans and cries to Heaven, or their ancient god, for mercy. +In truth, so difficult was the task of marshalling this motley array +at night, numbering as it did ten or twelve thousand souls, that a full +hour went by before the mob even began to move, slowly and uncertainly, +towards the place of refuge, whereof the opening was so narrow that but +few of them could pass it at a time. + +Meanwhile Hafela was developing the attack. Forming his great army into +the shape of a wedge he raised his battle-cry and rushed down on the +first line of fortifications, which he stormed without difficulty, +for they were defended by a few skirmishers only. Next he attacked the +second line, and carried it after heavy fighting, then hurled himself +upon the weakest point of the main fence of the vast kraal. Here it was +that the fray began in earnest, for here Nodwengo was waiting for him. +Thrice the thousands rolled on in the face of a storm of spears, and +thrice they fell back from the wide fence of thorns and the wall of +stone behind it. By now the battle had raged for about an hour and a +half, and it was reported to the king that the first of the women and +children had passed the archway into the valley, and that nearly all of +them were clear of the eastern gate of the town. + +“Then it is time that we follow them,” said the king, “for if we wait +here until the warriors of Hafela are among us, our retreat will become +a rout and soon there will be none left to follow. Let one company,” and +he named it, “hold the fence for a while to give us time to withdraw, +taking the wounded with us.” + +“We hear you, king,” said one of that company, “but our captain is +killed.” + +“Who among you will take over the command of these men and hold the +breach?” asked Nodwengo of the group of officers about him. + +“I, King,” answered old Hokosa, lifting his spear, “for I care not +whether I live or die.” + +“Go to, boaster!” cried another. “Who among us cares whether he lives or +dies when the king commands?” + +“That we shall know to-morrow,” said Hokosa quietly, and the soldiers +laughed at the retort. + +“So be it,” said the king, and while silently and swiftly he led off the +regiments, keeping in the shadow of the huts, Hokosa and his hundred +men posted themselves behind the weakened fence and wall. Now, for the +fourth time the attacking regiment came forward grimly, on this occasion +led by the prince himself. As they drew near, Hokosa leapt upon the +wall, and standing there in the bright moonlight where all could see +him, he called to them to halt. Instinctively they obeyed him. + +“Is it Hafela whom I see yonder?” he asked. + +“Ah! it is I,” answered the prince. “What would you with me, wizard and +traitor?” + +“This only, Hafela: I would ask you what you seek here?” + +“That which you promised me, Hokosa, the crown of my father and certain +other things.” + +“Then get you back, Hafela, for you shall never win them.. Have I +prophesied falsely to you at any time? Not so--neither do I prophesy +falsely now. Get you back whence you came, and your wolves with you, +else shall you bide here for ever.” + +“Do you dare to call down evil on me, Wizard?” shouted the prince +furiously. “Your wife is mine, and now I take your life also,” and with +all his strength he hurled at him the great spear he held. + +It hissed past Hokosa’s head, touching his ear, but he never flinched +from the steel. + +“A poor cast, Prince,” he said laughing; “but so it must have been, for +I am guarded by that which you cannot see. My wife you have, and she +shall be your ruin; my life you may take, but ere it leaves me, Hafela, +I shall see you dead and your army scattered. The Messenger is passed +away, but his power has fallen upon me and I speak the truth to you, O +Prince and warriors, who are--already dead.” + +Now a shriek of dismay and fury rose from the hundreds who heard this +prophesy of ill, for of Hokosa and his magic they were terribly afraid. + +“Kill him! Kill the wizard!” they shouted, and a rain of spears rushed +towards him on the wall. + +They rushed towards him, they passed above, below, around; but, of them +all, not one touched him. + +“Did I not tell you that I was guarded by That which you cannot see?” + Hokosa asked contemptuously. Then slowly he descended from the wall +amidst a great silence. + +“When men are scarce the tongue must play a part,” he explained to his +companions, who stared at him wondering. “By now the king and those +with him should have reached the eastern gate; whereas, had we fought at +once, Hafela would be hard upon his heels, for we are few, and who can +hold a buffalo with a rope of grass? Yet I think that I spoke truth +when I told him that the garment of the Messenger has fallen upon my +shoulders, and that death awaits him and his companions, as it awaits +me also and many of us. Now, friends, be ready, for the bull charges +and soon we must feel his horns. This at least is left to you, to die +gloriously.” + +While he was still speaking the first files of the regiment rushed upon +the fence, tearing aside the thorns with their hands till a passage was +made through them. Then they sprang upon the wall, there to be met +by the spears of Hokosa and his men thrusting upward from beneath its +shelter. Time after time they sprang, and time after time they fell back +dead or wounded, till at last, dashing forward in one dense column, they +poured over the stones as the rising tide pours over the rocks on the +sea-shore, driving the defenders before them by the sheer weight of +numbers. + +“This game is played!” cried Hokosa. “Fly now to the eastern gate, for +here we can do nothing more.” + +So they fled, those who survived of them, and after them came the +thousands of the foe, sacking and firing the deserted town as they +advanced. + +Hokosa and his men, or rather the half of them, reached the gate and +passed it in safety, barring it after them, and thereby delaying the +attackers till they could burst their way through. Now hundreds of huts +were afire, and the flames spread swiftly, lighting up the country far +and wide. In the glare of them, Hokosa could see that already a full +two-thirds of the crowd of fugitives had passed the narrow arch; while +Nodwengo and the soldiers were drawn up in companies upon the steep and +rocky slope that led to it, protecting their retreat. + +He advanced to the king and reported himself. + +“So you have lived through it,” said Nodwengo. + +“I shall die when my hour comes, and not before,” Hokosa answered. “We +did well yonder, and yet the most of us are alive to tell the tale, for +I knew when and how to go. Be ready, king, for the foe press us close, +and that mob behind us crawls onward like a snail.” + +As he spoke the pursuers broke through the fence and gate of the burning +town, and once more the fight began. They had the advantage of numbers; +but Nodwengo and his troops stood in a wide road upon higher ground +protected on either side by walls, and were, moreover, rested, not +breathless and weary with travel like the men of Hafela. Slowly, +fighting, every inch of the way, Nodwengo was pushed back, and slowly +the long ant-like line of women and sick and cattle crept through the +opening in the rock, till at length all of them were gone. + +“It is time,” said Nodwengo, glancing behind him, “for our arms grow +weary.” + +Then he gave orders, and company by company the defending force followed +on the path of the fugitives, till at length amidst a roar of rage and +disappointment, the last of them vanished through the arch, Hokosa among +them, and the place was blocked with stones, above which shone a hedge +of spears. + + + +CHAPTER XX + +NOMA SETS A SNARE + +Thus ended the first night’s battle, since for this time the enemy had +fought enough. Nodwengo and his men had also had enough, for out of the +five thousand of them some eleven hundred were killed or wounded. Yet +they might not rest, for all that night, assisted by the women, they +laboured, building stone walls across the narrowest parts of the valley. +Also the cattle, women and children were moved along the gorge, which in +shape may be compared to a bottle with two necks, one at either end, +and encamped in the opening of the second neck, where was the spring +of water. This spot was chosen both because here alone water could be +obtained, without which they could not hold out more than a single day, +and because the koppie whereon grew the strange-looking euphorbia known +as the Tree of Doom afforded a natural rampart against attack. + +Shortly after dawn, while the soldiers were resting and eating of +such food as could be procured--for the most part strips of raw or +half-cooked meat cut from hastily killed cattle--the onslaught was +renewed with vigour, Hafela directing his efforts to the forcing of the +natural archway. But, strive as he would, this he could not do, for it +was choked with stones and thorns and guarded by brave men. + +“You do but waste your labour, Hafela,” said Noma, who stood by him +watching the assault. + +“What then is to be done?” he asked, “for unless we come at them we +cannot kill them. It was clever of them to take refuge in this hole. I +thought surely that they would fight it out yonder, beneath the fences +of the Great Place.” + +“Ah!” she answered, “you forgot that they had Hokosa on their side. Did +you then think to catch him sleeping? This retreat was Hokosa’s counsel. +I learned it from the lips of that wounded captain before they killed +him. Now, it seems that there are but two paths to follow, and you can +choose between them. The one is to send a regiment a day and a half’s +journey across the cliff top to guard the further mouth of the valley +and to wait till these jackals starve in their hole, for certainly they +can never come out.” + +“It has started six hours since,” said Hafela, “and though the +precipices are steep, having the moon to travel by, it should reach the +river mouth of the valley before dawn to-morrow, cutting Nodwengo off +from the plains, if indeed he should dare to venture out upon them, +which, with so small a force, he will not do. Yet this first plan +of yours must fail, Noma, seeing that before they starve within, the +generals of Nodwengo will be back upon us from the mountains, catching +us between the hammer and the anvil, and I know not how that fight would +go.” + +“Yet, soon or late, it must be fought.” + +“Nay,” he answered, “for my hope is that should the _impi_ return to +find Nodwengo dead, they will surrender and acknowledge me as king, who +am the first of the blood royal. But what is your second plan?” + +By way of answer, she pointed to the cliff above them. On the right-hand +side, facing the archway, was a flat ledge overhanging the valley, at a +height of about a hundred feet. + +“If you can come yonder,” she said, “it will be easy to storm this gate, +for there lie rocks in plenty, and men cannot fight when stones are +dropping on their heads.” + +“But how can we come to that home of vultures, where never man has set +a foot? Look, the cliff above is sheer; no rock-rabbit could stand upon +it.” + +With her eye Noma measured the distance from the brink of the precipice +to the broad ledge commanding the valley. + +“Sixty paces, not more,” she said. “Well, yonder are oxen in plenty, and +out of their hides ropes can be made, and out of ropes a ladder, down +which men may pass; ten, or even five, would be enough.” + +“Well thought of Noma,” said Hafela. “Hokosa told us last night that to +him had passed the wisdom of the Messenger; but if this be so, I think +that to you has passed the guile of Hokosa.” + +“It seems to me that some of it abides with him,” answered Noma +laughing. + +Then the prince gave orders, and, with many workers of hides toiling at +it, within two hours the ladder was ready, its staves, set twenty inches +apart, being formed of knob-kerries, or the broken shafts of stabbing +spears. Now they lowered it from the top of the precipice so that its +end rested upon the ledge, and down it came several men, who swung upon +its giddy length like spiders on a web. Reaching this great shelf in +safety and advancing to the edge of it, these men started a boulder, +which, although as it chanced it hurt no one, fell in the midst of a +group of the defenders and bounded away through them. + +“Now we must be going,” said Hokosa, looking up, “for no man can fight +against rocks, and our spears cannot reach those birds. Had the army +been taught the use of the bow, as I counselled in the past days, we +might still have held the archway; but they called it a woman’s weapon, +and would have none of it.” + +As he spoke another stone fell, crushing the life out of a man who stood +next to him. Then they retreated to the first wall, which had been piled +up during the night, where it was not possible to roll rocks upon them +from the cliffs above. This wall, and others reared at intervals behind +it, they set to work to strengthen as much as they could, making the +most of the time that was left to them before the enemy could clear the +way and march on to attack. + +Presently Hafela’s men were through and sweeping down upon them with +a roar, thinking to carry the wall at a single rush. But in this they +failed; indeed, it as only after an hour’s hard fighting and by the +expedient of continually attacking the work with fresh companies that at +length they stormed the wall. + +When Hokosa saw that he could no longer hold the place, but before the +foe was upon him, he drew off his soldiers to the second wall, a quarter +of a mile or more away, and here the fight began again. And so it went +on for hour after hour, as one by one the fortifications were carried +by the weight of numbers, for the attackers fought desperately under the +eye of their prince, caring nothing for the terrible loss they suffered +in men. Twice the force of the defenders was changed by order of +Nodwengo, fresh men being sent from the companies held in reserve to +take the places of those who had borne the brunt of the battle. This +indeed it was necessary to do, seeing that it was impossible to carry +water to so many, and in that burning valley men could not fight for +long athirst. Only Hokosa stayed on, for they brought him drink in +a gourd, and wherever the fray was fiercest there he was always; nor +although spears were rained upon him by hundreds, was he touched by one +of them. + +At length as the night fell the king’s men were driven back from their +last scherm in the western half of the valley, across the open space +back upon the koppie where stood the Tree of Doom. Here they stayed a +while till, overmatched and outworn, they were pushed from its rocks +across the narrow stretch of broken ground into the shelter of the great +stone scherm or wall that ran from side to side of the further neck of +the valley, whereon thousands of women and such men as could be spared +had been working incessantly during the past night and day. + +It was as he retreated among the last upon this wall that Hokosa caught +sight of Noma for the first time since they parted in the house of the +Messenger. In the forefront of his troops, directing the attack, was +Hafela the prince, and at his side stood Noma, carrying in her hand a +little shield and a spear. At this moment also she saw him and called +aloud to him:-- + +“You have fought well, Wizard, but to-morrow all your magic shall avail +you nothing, for it will be your last day upon this earth.” + +“Ay, Noma,” he answered, “and yours also.” + +Then of a sudden a company of the king’s men rushed from the shelter of +the wall upon the attackers driving them back to the koppie and killing +several, so that in the confusion and gathering darkness Hokosa lost +sight of her, though a man at his side declared that he saw her fall +beneath the thrust of an assegai. Thus ended the second day. + +Now when the watch had been set the king and his captains took counsel +together, for their hearts were heavy. + +“Listen,” said Nodwengo: “out of five thousand soldiers a thousand have +been killed and a thousand lie among us wounded. Hark to the groaning of +them! Also we have with us women and children and sick to the number of +twelve thousand, and between us and those who would butcher them every +one there stands but a single wall. Nor is this the worst of it: the +spring cannot supply the wants of so great a multitude in this hot +place, and it is feared that presently the water will be done. What +way shall we turn? If we surrender to Hafela, perhaps he will spare the +lives of the women and children; but whatever he may promise, the most +of us he will surely slay. If we fight and are defeated, then once +his regiments are among us, all will be slain according to the ancient +custom of our people. I have bethought me that we might retreat through +the valley, but the river beyond is in flood; also it is certain that +before this multitude could reach it, the prince will have sent a force +to cut us off while he himself harasses our rear. Now let him who has +counsel speak.” + +“King, I have counsel,” said Hokosa. “What were the words that the +Messenger spoke to us before he died? Did he not say: ‘Even now the +heathen is at your gates, and many of you shall perish on his spears; +but I tell you that he shall not conquer’? Did he not say: ‘Be faithful, +cling to the Cross, and do not dare to doubt your Lord, for He will +protect you, and your children after you, and He will be your Captain +and you shall be His people’? Did he not bid you also to listen to my +counsel? Then listen to it, for it is his: Your case seems desperate, +but have no fear, and take no thought for the morrow, for all shall yet +be well. Let us now pray to Him that the Messenger has revealed to us, +and Whom now he implores on our behalf in that place where he is to +guide us and to save us, for then surely He will hearken to our prayer.” + +“So be it,” said Nodwengo, and going out he stood upon a pillar of stone +in the moonlight and offered up his supplication in the hearing of the +multitude. + +Meanwhile, those of the camp of Hafela were also taking counsel. They +had fought bravely indeed, and carried the schanses; but at great cost, +since for every man that Nodwengo had lost, three of theirs had fallen. +Moreover, they were in evil case with weariness and the want of water, +as each drop they drank must be carried to them from the Great Place in +bags made of raw hide, which caused it to stink, for they had but few +gourds with them. + +“Now it is strange,” said Hafela, “that these men should fight so +bravely, seeing that they are but a handful. There can be scarce three +thousand of them left, and yet I doubt not that before we carry those +last walls of theirs as many of us or more will be done. Ay! and after +they are done with, we must meet their great _impi_ when it returns, and +of what will befall us then I scarcely like to think.” + +“Ill-fortune will befall you while Hokosa lives,” broke in Noma. “Had it +not been for him, this trouble would have been done with by now; but +he is a wizard, and by his wizardries he defeats us and puts heart into +Nodwengo and the warriors. You, yourself, have seen him this day defying +us, not once but many times, for upon his flesh steel has no power. Ay! +and this is but the beginning of evil, for I am sure that he leads you +into some deep trap where you shall perish everlastingly. Did he not +himself declare that the power of that dead white worker of miracles has +fallen upon him, and who can fight against magic?” + +“Who, indeed?” said Hafela humbly; for like all savages he was +very superstitious, and, moreover, a sincere believer in Hokosa’s +supernatural capacities. “This wizard is too strong for us; he is +invulnerable, and as I know well he can read the secret thoughts of men +and can suck wisdom from the dead, while to his eyes the darkness is no +blind.” + +“Nay, Hafela,” answered Noma, “there is one crack in his shield. Hear +me: if we can but catch him and hold him fast we shall have no need to +fear him more, and I think that I know how to bait the trap.” + +“How will you bait it?” asked Hafela. + +“Thus. Midway between the koppie and the wall behind which lie the men +of the king stands a flat rock, and all about that rock are stretched +the bodies of dead soldiers. Now, this is my plan: that when next one +of those dark storm-clouds passes over the face of the moon six of the +strongest of our warriors should creep upon their bellies down this way +and that, as though they were also numbered with the slain. This done, +you shall despatch a herald to call in the ears of the king that you +desire to treat with him of peace. Then he will answer that if this be +so you can come beneath the walls of his camp, and your herald shall +refuse, saying that you fear treachery. But he must add that if Nodwengo +will bid Hokosa to advance alone to the flat rock, you will bid me, +Noma, whom none can fear, to do likewise, and that there we can talk in +sight of both armies, and returning thence, make report to you and to +Nodwengo. Afterwards, so soon as Hokosa has set his foot upon the rock, +those men who seem to be dead shall spring upon him and drag him to +our camp, where we can deal with him; for once the wizard is taken, the +cause of Nodwengo is lost.” + +“A good pitfall,” said the prince; “but will Hokosa walk into the trap?” + +“I think so, Hafela, for three reasons. He is altogether without fear; +he will desire, if may be, to make peace on behalf of the king; and he +has this strange weakness, that he still loves me, and will scarcely +suffer an occasion of speaking with me to go past, although he has +divorced me.” + +“So be it,” said the prince; “the game can be tried, and if it fails, +why we lose nothing, whereas if it succeeds we gain Hokosa, which is +much; for with you I think that our arms will never prosper while that +accursed wizard sits yonder weaving his spells against us, and bringing +our men to death by hundreds and by thousands.” + +Then he gave his orders, and presently, when a cloud passed over the +face of the moon, six chosen men crept forward under the lee of the flat +rock and threw themselves down here and there amongst the dead. + +Soon the cloud passed, and the herald advanced across the open space +blowing a horn, and waving a branch in his hand to show that he came +upon a mission of peace. + + + +CHAPTER XXI + +HOKOSA IS LIFTED UP + +“What would you?” asked Hokosa of the herald as he halted a short +spear-cast from the wall. + +“My master, the Prince Hafela, desires to treat with your master, +Nodwengo. Many men have fallen on either side, and if this war goes on, +though victory must be his at last, many more will fall. Therefore, if +any plan can be found, he desires to spare their lives.” + +Now Hokosa spoke with the king, and answered:-- + +“Then let Hafela come beneath the wall and we will talk with him.” + +“Not so,” answered the herald. “Does a buck walk into an open pit? Were +the prince to come here it might chance that your spears would talk with +him. Let Nodwengo follow me to the camp yonder, where we promise him +safe conduct.” + +“Not so,” answered Hokosa. “‘Does a buck walk into an open pit?’ Set out +your message, and we will consider it.” + +“Nay, I am but a common man without authority; but I am charged to make +you another offer, and if you will not hear it then there is an end. +Let Hokosa advance alone to that flat rock you see yonder, and there he +shall be met, also alone, by one having power to talk with him, namely, +by the Lady Noma, who was once his wife. Thus they can confer together +midway between the camps and in full sight of both of them, nor, no man +being near, can he find cause to be afraid of an unarmed girl. What say +you?” + +Hokosa turned and talked with the king. + +“I think it well that you should not go,” said Nodwengo. “The offer +seems fair, and the stone is out of reach of their spears; still, +behind it may lurk a scheme to kill or capture you, for Hafela is very +cunning.” + +“It may be so, King,” answered Hokosa; “still, my heart tells me it is +wisest that I should do this thing, for our case is desperate, and if I +do it not, that may be the cause of the death of all of us to-morrow. +At the worst, I am but one man, and it matters little what may chance to +me; nor shall I come to any harm unless it is the will of Heaven that it +should be so; and be sure of this, that out of the harm will arise good, +for where I go there the spirit of the Messenger goes with me. Remember +that he bade you listen to my counsel while I remain with you, seeing +that I do not speak of my own wisdom. Therefore let me go, and if it +should chance that I am taken, trouble not about the matter, for thus it +will be fated to some great end. Above all, though often enough I have +been a traitor in the past, do not dream that I betray you, keeping in +mind that so to do would be to betray my own soul, which very soon must +render its account on high.” + +“As you will, Hokosa,” answered the king. “And now tell those rebel dogs +that on these terms only will I make peace with them--that they withdraw +across the mountains by the path which their women and children have +taken, leaving this land for ever without lifting another spear against +us. If they will do this, notwithstanding all the wickedness and +slaughter that they have worked, I will send command to my _impi_ to let +them go unharmed. If they will not do this, I put my trust in the God I +worship and will fight this fray out to the end, knowing that if I and +my people perish, they shall perish also.” + +Now Nodwengo himself spoke to the herald who was waiting beyond the +wall. + +“Go back to him you serve,” he said, “and say that Hokosa will meet her +who was his wife upon the flat stone and talk with her in the sight of +both armies, bearing my word with him. At the sound of the blowing of a +horn shall each of them advance unarmed and alone from either camp. Say +to my brother also that it will indeed be ill for him if he attempts +treachery upon Hokosa, for the man who causes his blood to flow will +surely die, and after death shall be accursed for ever.” + +The herald went, and presently a horn was blown. + +“Now it comes into my mind that we part for the last time,” said +Nodwengo in a troubled voice as he took the hand of Hokosa. + +“It may be so, King; in my heart I think that it is so; yet I do not +altogether grieve thereat, for the burden of my past sins crushes me, +and I am weary and seek for rest. Yet we do not part for the last time, +because whatever chances, in the end I shall make my report to you +yonder”--and he pointed upwards. “Reign on for long years, King--reign +well and wisely, clinging to the Faith, for thus at the last shall you +reap your reward. Farewell!” + +Now again the horn blew, and in the bright moonlight the slight figure +of Noma could be seen advancing towards the stone. + +Then Hokosa sprang from the wall and advanced also, till at the same +moment they climbed upon the stone. + +“Greeting, Hokosa,” said Noma, and she stretched out her hand to him. + +By way of answer he placed his own behind his back, saying: “To your +business, woman.” Yet his eyes searched her face--the face which in his +folly he still loved; and thus it came about that he never saw sundry of +the dead bodies, which lay in the shadow of the stone, begin to quicken +into life, and inch by inch to arise, first to their knees and next to +their feet. He never saw or heard them, yet, as the words left his lips, +they sprang upon him from every side, holding him so that he could not +move. + +“Away with him!” cried Noma with a laugh of triumph; and at her command +he was half-dragged and half-carried across the open space and thrust +violently over a stone wall into the camp of Hafela. + +Now Nodwengo and his soldiers saw what had happened, and with a shout of +“Treachery!” some hundreds of them leapt into the plain and began to run +towards the koppie to rescue their envoy. + +Hokosa heard the shout, and wrenching himself round, beheld them. + +“Back!” he cried in a clear, shrill voice. “Back! children of Nodwengo, +and leave me to my fate, for the foe waits for you by thousands behind +the wall!” + +A soldier struck him across the mouth, bidding him be silent; but his +warning had come to the ears of Nodwengo, causing him and his warriors +to halt and begin a retreat. It was well that they did so, for seeing +that they would not come on, from under the shelter of the wall and +of every rock and stone soldiers jumped up by companies and charged, +driving them back to their own schanse. But the king’s men had the start +of them, and had taken shelter behind it, whence they greeted them with +a volley of spears, killing ten and wounding twice as many more. + +Now it was Hokosa’s turn to laugh, and laugh he did, saying:-- + +“My taking is well paid for already, Prince. A score of your best +warriors is a heavy price to give for the carcase of one weary and aging +man. But since I am here among you, captured with so much pain and loss, +tell me of your courtesy why I have been brought.” + +Then the prince shook his spear at him and cursed him. + +“Would you learn, wizard and traitor?” he cried. “We have caught you +because we know well that while you stay yonder your magic counsel will +prevail against our might; whereas, when once we hold you fast, Nodwengo +will wander to his ruin like a blind and moonstruck man, for you were to +him both eyes and brain.” + +“I understand,” said Hokosa calmly. “But, Prince, how if I left my +wisdom behind me?” + +“That may not be,” answered Hafela, “since even a wizard cannot throw +his thoughts into the heart of another from afar.” + +“Ah! you think so, Prince. Well, ask Noma yonder if I cannot throw my +thoughts into her heart from afar: though of late I have not chosen +to do so, having put aside such spells. But let it pass, and tell me, +having taken me, what is it you propose to do with me? First, however, +I will give you for nothing some of that wisdom which you grudge to +Nodwengo the king. Be advised by me, Prince, and take the terms that he +offers to you--namely, to turn this very night and begone from the land +without harm or hindrance. Will you receive my gift, Hafela?” + +“What will happen if I refuse it?” asked the prince slowly. + +Now Hokosa looked at the dust at his feet, then he gazed upwards +searching the heavens, and answered:-- + +“Did not I tell you yesterday? I think that this will happen. I +think--but who can be quite sure of the future, Hafela?--that you and +the most of your army by this hour to-morrow night will be lying fast +asleep about this place, with jackals for your bedfellows.” + +The prince heard and trembled at his words, for he believed that if he +willed it, Hokosa could prophesy the truth. + +“Accursed dog!” he said. “I am minded to be guided by your saying; but +be sure of this, that if I follow it, you shall stay here to sleep with +jackals, yes, this very night.” + +Then Noma broke in. + +“Be not mad, Hafela!” she said. “Will you listen to the lies that this +renegade tells to work upon your fears? Will you abandon victory when it +lies within your grasp, and in place of a great king become a fugitive +whom all men mock at, an outcast to be hunted down at leisure by that +brother against whom you dared to rebel, but on whom you did not dare +to shut your hand when he lay in its hollow? Silence the tongue of this +captive rogue for ever and become a man again, with the heart of a man.” + +“Now,” said Hokosa gently; “many would find it hard to believe that I +reared this woman from childhood, nursing her with my own hands when +she was sick and giving her of the best I had; that afterwards, when +you stole her from me, Prince, I sinned deeply to win her back. That +I married her and sinned yet more deeply to give her the greatness she +desired; and at last, of my own will, I loosed the bonds by which I held +her, although I could not thrust her memory from my heart. Yet I have +earned it all, for I made her the tool of my witchcraft, and therefore +it is just that she should turn and rend me. Well, if you like it, take +her counsel, Prince, and let mine go, for I care nothing which you take; +only, forgive me if I prophesy once more and for the last time--I am +sure that Nodwengo yonder spoke truth when he bade your herald tell +me that he who causes my blood to flow shall surely die and for it be +called to a strict account. Prince, I am a Christian now, and believe +me, whatever you may do, I seek no revenge upon you; having been myself +forgiven so much, in my turn I have learned to forgive. Yet it may be +ill for that man who causes my blood to flow.” + +“Let him be strangled,” said a captain who stood near by, “and then +there will be no blood in the matter.” + +“Friend,” answered Hokosa, “you should have been not a soldier but a +pleader of causes. True it is then that the prince will only cause my +life to fly, but whether that is a smaller sin I leave you to judge.” + +“Keep him prisoner,” said another, “till we learn how these matters +end.” + +“Nay,” answered Hafela, “for then he will surely outwit us and escape. +Noma, what shall we do with this man who was your husband? Tell us, for +you should know best how to deal with him.” + +“Let me think,” she answered, and she looked first at the ground beneath +her, next around her, then upwards toward the skies. + +Now they stood at the foot of the koppie, on the flat top of which grew +the great Tree of Doom, that for generations had served the People of +Fire as a place of execution of their criminals, or of those who fell +under the ban of the king or of the witch-doctors. Among and above the +finger-like fronds of this strange and dreadful-looking tree towered +that white dead limb shaped like a cross, which Owen had pointed out +to his disciple John, taking it to be a sign and a promise. This cross +stood out clear against the sinking moon. It caught Noma’s eye, and a +devilish thought entered into her heart. + +“You would keep this fellow alive?” she said, “and yet you would +not suffer him to escape. See, there above you is a cross such as he +worships. Bind him to it as he says the Man whom he worships was bound, +and let that dead Man help him if he may.” + +The prince and those about Noma shrank back a little in horror. They +were cruel men rendered more cruel by their superstitious fear of one +whom they believed to be uncanny; one to whom they attributed inhuman +powers which he was exercising to their destruction, but still this +doom seemed dreadful to them. Noma read their minds and went on +passionately:-- + +“You deem me unmerciful, but you do not know what I have suffered at +this wizard’s hands. For his sake and because of him I am haunted. For +his own purposes he opened the gates of Distance, he sent me down among +the dwellers in Death, causing me to interpret their words for him. I +did so, but the dwellers came back out of Death with me, and from that +hour they have not left me, nor will they ever leave me; for night by +night they sojourn at my side, tormenting me with terrors. He has +told me that through my mouth that spirit whom he drew into my body +prophesied that he should be ‘lifted up above the people.’ Let the +prophecy be fulfilled, let him be lifted up, for then perchance the +ghosts will depart from me and I shall win peace and sleep. Also, thus +alone can you hold him safe and yet shed no blood.” + +“Be it so,” said the prince. “When we plotted together of the death of +the king, and as your price, Hokosa, you bargained for the girl whom I +had chosen to wife, did I not warn you that this witch of many spells, +who holds both our hearts in her little hands, should yet hound you to +death and mock you while you perished by an end of shame? What did I +tell you, Hokosa?” + +Now when he heard his fate, Hokosa bowed his head and trembled a little. +Then he lifted it, and exclaimed in a clear voice:-- + +“It is true, Prince, but I will add to your words. She shall bring +_both_ of us to death. For me, I am honoured indeed in that there has +been allotted to me that same end which my Master chose. To that cross +let my sins be fastened and with them my body.” + +Now the moon sank, but in the darkness men were found who dared to climb +the tree, taking with them strips of raw hide. They reached the top of +it, four of them, and seating themselves upon the arms of the cross, +they let down a rope, the noose of which was placed about the body of +Hokosa. As it tightened upon him, he turned his calm and dreadful eyes +on to the eyes of Noma and said to her:-- + +“Woman, I do not reproach you; but I lay this fate upon you, that you +shall watch me die. Thereafter, let God deal with you as He may choose.” + +Now, when she heard these words Noma shrieked aloud, for of a sudden she +felt that the power of the will of Hokosa, from which she had been freed +by him, had once more fallen upon her, and that come what might she was +doomed to obey his last commands. + +Little by little the soldiers drew him up and in the darkness they bound +him fast there upon the lofty cross. Then they descended and left him, +and would have led Noma with them from the tree. But this they could +not do, for always she broke from them screaming, and fled back to its +shadow. + +Then, seeing that she was bewitched, Hafela commanded that they should +bind a cloth about her mouth and leave her there till her senses +returned to her in the sunlight--for none of them dared to stop with +her in the shadow of that tree, since the odours of it were poisonous to +man. Also they believed the place to be haunted by evil spirits. + + + +CHAPTER XXII + +THE VICTORY OF THE CROSS + +The sun rose suddenly over the edge of the cliffs, and while it was yet +deep shadow in the valley, its red light struck upon the white cross of +perished wood that towered above the Tree of Doom and on the black +shape of Hokosa crucified to it living. The camp of the king saw and +understood, and from every throat of the thousands of men, women and +children gathered there, went up a roar of rage and horror. The king +lifted his hand, and silence fell upon the place; then he mounted on the +wall and cried aloud:-- + +“Do you yet live, Hokosa, or is it your body only that those traitors +have fastened to the tree?” + +Back came the answer through the clear still air:-- + +“I live, O King!” + +“Endure then a little while,” called Nodwengo, “and we will storm the +tree and save you.” + +“Nay,” answered Hokosa, “you cannot save me; yet before I die I shall +see you saved.” + +Then his words were lost in tumult, for the third day’s fighting began. +Desperately the regiments of Hafela rushing across the open space, +hurled themselves upon the fortifications, which, during the night, had +been strengthened by the building of two inner walls. Nor was this all, +for suddenly a cry told those in front that the regiment which Hafela +had despatched across the mountains had travelled up the eastern neck of +the valley, and were attacking the position in their rear. Well was it +for Nodwengo now that he had listened to the counsel of Hokosa, and, +wearied as his soldiers were, had commanded that here also a great wall +should be built. + +For two hours the fight raged, and then on either side the foe fell +back, not beaten indeed, though their dead were many, but to rest and +take counsel. But now a new trouble arose: from all the camp of Nodwengo +there went up a moan of pain to Heaven, for since the evening of +yesterday the spring had given out, and they had found no water +wherewith to wet their lips. During the night they bore it; but now the +sun beating down on the black rocks with fearful force scorched them to +the marrow, till they began to wither like fallen leaves, and already +wounded men and children died, while the warriors cut the throats of +oxen and drank their blood. + +Hokosa hanging on his cross heard this moaning and divined its cause. + +“Be of good comfort, children of Nodwengo,” he cried; “for I will pray +that rain be sent upon you.” And he lifted his head and prayed. + +Now, whether it was by chance or whether his prayer was heard, who can +say? At least it happened that immediately thereafter clouds began to +gather and to thicken in the blue of Heaven, and within two hours rain +fell in torrents, so that every one could drink his fill, and the spring +being replenished at its sources, flowed again strongly. + +After the rain came cold and moaning winds, and after the wind a great +gloom and thunder. + +Now, taking advantage of the shadow, the regiments of Hafela renewed +their attack, and this time they carried the first of the three walls, +for its defenders grew feeble and few in number. There they paused a +while, and save for the cries of the wounded and of frightened women, +the silence was great. + +“Let your hearts be filled up!” cried the voice of Hokosa through the +silence; “for the sunlight shines upon the plain of the Great Place +yonder, and in it I see the sheen of spears. The _impi_ travels to your +aid, O children of Nodwengo.” + +Now, at this tidings the people of the king shouted for joy; but +Hafela called to his regiments to make an end of them, and they hurled +themselves upon the second wall, fighting desperately. Again and again +they were beaten back, and again and again they came on, till at +length they carried this wall also, driving its defenders, or those who +remained alive of them, into the third entrenchment, and paused to rest +awhile. + +“Pray for us, O Prophet who are set on high!” cried a voice from the +camp, “for if succour do not reach us speedily, we are sped.” + +Before the echoes of the voice had died away, a flash of lightning +flared through the gloom, and in the light of it Hokosa saw that the +king’s _impi_ was rushing up the gorge. + +“Fight on! Fight on!” he called in answer. “I have prayed to Heaven, and +your succour is at hand.” + +Then, with a howl of rage, Hafela’s regiments hurled themselves upon +the third and last entrenchment, attacking it at once in front and rear. +Twice they nearly carried it, but each time the wild scream of Hokosa +on high was heard above the din, conjuring its defenders to fight on and +fear not, for Heaven had sent them help. They fought as men have seldom +fought before, and with them fought the women and even the children. +They were few and the foe was still many, but they listened to the +urging of him whom they believed to be inspired in his death-agony upon +the cross above them, and still they held their own. Twice portions of +the wall were torn down, but they filled the breach with the corpses of +the dead, ay! and with the bodies of the living, for the wounded, +the old men and the very women piled themselves there in the place of +stones. No such fray was told of in the annals of the People of Fire as +this, the last stand of Nodwengo against the thousands of Hafela. Now +all the shouting had died away, for men had no breath left wherewith to +shout, only from the gloomy place of battle came low groans and the deep +sobbing sighs of warriors gripped in the death-hug. + +“_Fight on! Fight on!_” shrilled the voice of Hokosa on high. “Lo! +the skies are open to my dying sight, and I see the _impis_ of Heaven +sweeping to succour you. _Behold!_” + +They dashed the sweat from their eyes and looked forth, and as they +looked, the pall of gloom was lifted, and in the golden glow of +many-shafted light, they saw, not the legions of Heaven indeed, but the +regiments of Nodwengo rushing round the bend of the valley, as dogs rush +upon a scent, with heads held low and spears outstretched. + +Hafela saw them also. + +“Back to the koppie,” he cried, “there to die like men, for the +wizardries of Hokosa have been too strong for us, and lost is this my +last battle and the crown I came to seek!” + +They obeyed, and all that were left of them, some ten thousand men, they +ran to the koppie and formed themselves upon it, ring above ring, and +here the soldiers of Nodwengo closed in upon them. + +Again and for the last time the voice of Hokosa rang out above the fray. + +“Nodwengo,” he cried, “with my passing breath I charge you have mercy +and spare these men, so many of them as will surrender. The day of +bloodshed has gone by, the fray is finished, the Cross has conquered. +Let there be peace in the land.” + +All men heard him, for his piercing scream, echoed from the precipices, +came to the ears of each. All men heard him, and, even in that fierce +hour of vengeance, all obeyed. The spear that was poised was not thrown, +and the kerry lifted over the fallen did not descend to dash away his +life. + +“Hearken, Hafela!” called the king, stepping forward from the ranks of +the attackers. “He whom you have set on high to bring defeat upon you +charges me to give you peace, and in the name of the conquering Cross I +give peace. All who surrender shall dwell henceforth in my shadow, nor +shall the head or the heel of one of them be harmed, although their sin +is great. One life only will I take, the life of that witch who +brought your armies down upon me to burn my town and slay my people +by thousands, and who but last night betrayed Hokosa to his death of +torment. All shall go free, I say, save the witch; and for you, you +shall be given cattle and such servants as will cling to you to the +number of a hundred, and driven from the land. Now, what say you? Will +you yield or be slain? Swift with your answer; for the sun sinks, and +ere it is set there must be an end in this way or in that.” + +The regiments of Hafela heard, and shouted in answer as with one +voice:-- + +“We take your mercy, King! We fought bravely while we could, and now we +take your mercy, King!” + +“What say you, Hafela?” repeated Nodwengo, addressing the prince, who +stood upon a point of rock above him in full sight of both armies. + +Hafela turned and looked at Hokosa hanging high in mid-air. + +“What say I?” he answered in a slow and quiet voice. “I say that the +Cross and its Prophet have been too strong for me, and that I should +have done well to follow the one and to listen to the counsel of the +other. My brother, you tell me that I may go free, taking servants with +me. I thank you and I will go--alone.” + +And setting the handle of his spear upon the rock, with a sudden +movement he fell forward, transfixing his heart with its broad blade, +and lay still. + +“At least he died like one of the blood-royal of the Sons of Fire!” + cried Nodwengo, while the armies stood silent and awestruck, “and with +the blood-royal he shall be buried. Lay down your arms, you who followed +him and fought for him, fearing nothing, and give over to me the witch +that she may be slain.” + +“She hides under the tree yonder!” cried a voice. + +“Go up and take her,” said Nodwengo to some of his captains. + +Now Noma, crouched on the ground beneath the tree, had seen and heard +all that passed. Perceiving the captains making their way towards her +through the lines of the soldiers, who opened out a path for them, she +rose and for a moment stood bewildered. Then, as though drawn by some +strange attraction, she turned, and seizing hold of the creeper that +clung about it, she began to climb the Tree of Doom swiftly. Up she went +while all men watched, higher and higher yet, till passing out of the +finger-like foliage she reached the cross of dead wood whereto Hokosa +hung, and placing her feet upon one arm of it, stood there, supporting +herself by the broken top of the upright. + +Hokosa was not yet dead, though he was very near to death. Lifting his +glazing eyes, he knew her and said, speaking thickly:-- + +“What do you here, Noma, and wherefore have you come?” + +“I come because you draw me,” she answered, “and because they seek my +life below.” + +“Repent, repent!” he whispered, “there is yet time and Heaven is very +merciful.” + +She heard, and a fury seized her. + +“Be silent, dog!” she cried. “Having defied your God so long, shall I +grovel to Him at the last? Having hated you so much, shall I seek your +forgiveness now? At least of one thing I am glad--it was I who brought +you here, and with me and through me you shall die.” + +Then, placing one foot upon his bent head as if in scorn, she leaned +forward, her long hair flying to the wind, and cursed Nodwengo and his +people, naming them renegades and apostates, and cursed the soldiers of +Hafela, naming them cowards, calling down upon them the malison of their +ancestors. + +Hokosa heard and muttered:-- + +“For your soul’s sake, woman, repent! repent, ere it be too late!” + +“Repent!” she screamed, catching at his words. “Thus do I repent!” + and drawing the knife from her girdle, she leant over him and drove it +hilt-deep into his breast. + +Then with a sudden movement she sprang upwards and outwards into the +air, and rushing down through a hundred feet of space, was struck dead +upon that very rock where the corpse of Hafela lay. + +Now, beneath the agony of the life Hokosa lifted his head for the last +time, crying in a great voice:-- + +“Messenger, I come, be you my guide,” and with the words his soul +passed. + +“All is over and ended,” said a voice. “Soldiers, salute the king with +the royal salute.” + +“Nay,” answered Nodwengo. “Salute me not, salute the Cross and him who +hangs thereon.” + +So, while the rays of the setting sun shone about it, regiment by +regiment that great army rushed past the koppie, and pausing opposite to +the cross and its burden, they rendered to it the royal salute of kings. + +***** + +Then the night fell, and thus through the power of Faith that now, as of +old, is the only true and efficient magic, was accomplished the mission +to the Sons of Fire of the Saint and Martyr, Thomas Owen, and of his +murderer and disciple, the Wizard Hokosa. + + + + + +End of the Project Gutenberg EBook of The Wizard, by H. Rider Haggard + +*** END OF THIS PROJECT GUTENBERG EBOOK THE WIZARD *** + +***** This file should be named 2893-0.txt or 2893-0.zip ***** +This and all associated files of various formats will be found in: + http://www.gutenberg.org/2/8/9/2893/ + +Produced by John Bickers; Dagny; David Widger + +Updated editions will replace the previous one--the old editions +will be renamed. + +Creating the works from public domain print editions means that no +one owns a United States copyright in these works, so the Foundation +(and you!) can copy and distribute it in the United States without +permission and without paying copyright royalties. Special rules, +set forth in the General Terms of Use part of this license, apply to +copying and distributing Project Gutenberg-tm electronic works to +protect the PROJECT GUTENBERG-tm concept and trademark. Project +Gutenberg is a registered trademark, and may not be used if you +charge for the eBooks, unless you receive specific permission. If you +do not charge anything for copies of this eBook, complying with the +rules is very easy. You may use this eBook for nearly any purpose +such as creation of derivative works, reports, performances and +research. They may be modified and printed and given away--you may do +practically ANYTHING with public domain eBooks. Redistribution is +subject to the trademark license, especially commercial +redistribution. + + + +*** START: FULL LICENSE *** + +THE FULL PROJECT GUTENBERG LICENSE +PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK + +To protect the Project Gutenberg-tm mission of promoting the free +distribution of electronic works, by using or distributing this work +(or any other work associated in any way with the phrase “Project +Gutenberg”), you agree to comply with all the terms of the Full Project +Gutenberg-tm License (available with this file or online at +http://gutenberg.org/license). + + +Section 1. General Terms of Use and Redistributing Project Gutenberg-tm +electronic works + +1.A. By reading or using any part of this Project Gutenberg-tm +electronic work, you indicate that you have read, understand, agree to +and accept all the terms of this license and intellectual property +(trademark/copyright) agreement. If you do not agree to abide by all +the terms of this agreement, you must cease using and return or destroy +all copies of Project Gutenberg-tm electronic works in your possession. +If you paid a fee for obtaining a copy of or access to a Project +Gutenberg-tm electronic work and you do not agree to be bound by the +terms of this agreement, you may obtain a refund from the person or +entity to whom you paid the fee as set forth in paragraph 1.E.8. + +1.B. “Project Gutenberg” is a registered trademark. It may only be +used on or associated in any way with an electronic work by people who +agree to be bound by the terms of this agreement. There are a few +things that you can do with most Project Gutenberg-tm electronic works +even without complying with the full terms of this agreement. See +paragraph 1.C below. There are a lot of things you can do with Project +Gutenberg-tm electronic works if you follow the terms of this agreement +and help preserve free future access to Project Gutenberg-tm electronic +works. See paragraph 1.E below. + +1.C. The Project Gutenberg Literary Archive Foundation (“the Foundation” + or PGLAF), owns a compilation copyright in the collection of Project +Gutenberg-tm electronic works. Nearly all the individual works in the +collection are in the public domain in the United States. If an +individual work is in the public domain in the United States and you are +located in the United States, we do not claim a right to prevent you from +copying, distributing, performing, displaying or creating derivative +works based on the work as long as all references to Project Gutenberg +are removed. Of course, we hope that you will support the Project +Gutenberg-tm mission of promoting free access to electronic works by +freely sharing Project Gutenberg-tm works in compliance with the terms of +this agreement for keeping the Project Gutenberg-tm name associated with +the work. You can easily comply with the terms of this agreement by +keeping this work in the same format with its attached full Project +Gutenberg-tm License when you share it without charge with others. + +1.D. The copyright laws of the place where you are located also govern +what you can do with this work. Copyright laws in most countries are in +a constant state of change. If you are outside the United States, check +the laws of your country in addition to the terms of this agreement +before downloading, copying, displaying, performing, distributing or +creating derivative works based on this work or any other Project +Gutenberg-tm work. The Foundation makes no representations concerning +the copyright status of any work in any country outside the United +States. + +1.E. Unless you have removed all references to Project Gutenberg: + +1.E.1. The following sentence, with active links to, or other immediate +access to, the full Project Gutenberg-tm License must appear prominently +whenever any copy of a Project Gutenberg-tm work (any work on which the +phrase “Project Gutenberg” appears, or with which the phrase “Project +Gutenberg” is associated) is accessed, displayed, performed, viewed, +copied or distributed: + +This eBook is for the use of anyone anywhere at no cost and with +almost no restrictions whatsoever. You may copy it, give it away or +re-use it under the terms of the Project Gutenberg License included +with this eBook or online at www.gutenberg.org + +1.E.2. If an individual Project Gutenberg-tm electronic work is derived +from the public domain (does not contain a notice indicating that it is +posted with permission of the copyright holder), the work can be copied +and distributed to anyone in the United States without paying any fees +or charges. If you are redistributing or providing access to a work +with the phrase “Project Gutenberg” associated with or appearing on the +work, you must comply either with the requirements of paragraphs 1.E.1 +through 1.E.7 or obtain permission for the use of the work and the +Project Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or +1.E.9. + +1.E.3. If an individual Project Gutenberg-tm electronic work is posted +with the permission of the copyright holder, your use and distribution +must comply with both paragraphs 1.E.1 through 1.E.7 and any additional +terms imposed by the copyright holder. Additional terms will be linked +to the Project Gutenberg-tm License for all works posted with the +permission of the copyright holder found at the beginning of this work. + +1.E.4. Do not unlink or detach or remove the full Project Gutenberg-tm +License terms from this work, or any files containing a part of this +work or any other work associated with Project Gutenberg-tm. + +1.E.5. Do not copy, display, perform, distribute or redistribute this +electronic work, or any part of this electronic work, without +prominently displaying the sentence set forth in paragraph 1.E.1 with +active links or immediate access to the full terms of the Project +Gutenberg-tm License. + +1.E.6. You may convert to and distribute this work in any binary, +compressed, marked up, nonproprietary or proprietary form, including any +word processing or hypertext form. However, if you provide access to or +distribute copies of a Project Gutenberg-tm work in a format other than +“Plain Vanilla ASCII” or other format used in the official version +posted on the official Project Gutenberg-tm web site (www.gutenberg.org), +you must, at no additional cost, fee or expense to the user, provide a +copy, a means of exporting a copy, or a means of obtaining a copy upon +request, of the work in its original “Plain Vanilla ASCII” or other +form. Any alternate format must include the full Project Gutenberg-tm +License as specified in paragraph 1.E.1. + +1.E.7. Do not charge a fee for access to, viewing, displaying, +performing, copying or distributing any Project Gutenberg-tm works +unless you comply with paragraph 1.E.8 or 1.E.9. + +1.E.8. You may charge a reasonable fee for copies of or providing +access to or distributing Project Gutenberg-tm electronic works provided +that + +- You pay a royalty fee of 20% of the gross profits you derive from + the use of Project Gutenberg-tm works calculated using the method + you already use to calculate your applicable taxes. The fee is + owed to the owner of the Project Gutenberg-tm trademark, but he + has agreed to donate royalties under this paragraph to the + Project Gutenberg Literary Archive Foundation. Royalty payments + must be paid within 60 days following each date on which you + prepare (or are legally required to prepare) your periodic tax + returns. Royalty payments should be clearly marked as such and + sent to the Project Gutenberg Literary Archive Foundation at the + address specified in Section 4, “Information about donations to + the Project Gutenberg Literary Archive Foundation.” + +- You provide a full refund of any money paid by a user who notifies + you in writing (or by e-mail) within 30 days of receipt that s/he + does not agree to the terms of the full Project Gutenberg-tm + License. You must require such a user to return or + destroy all copies of the works possessed in a physical medium + and discontinue all use of and all access to other copies of + Project Gutenberg-tm works. + +- You provide, in accordance with paragraph 1.F.3, a full refund of any + money paid for a work or a replacement copy, if a defect in the + electronic work is discovered and reported to you within 90 days + of receipt of the work. + +- You comply with all other terms of this agreement for free + distribution of Project Gutenberg-tm works. + +1.E.9. If you wish to charge a fee or distribute a Project Gutenberg-tm +electronic work or group of works on different terms than are set +forth in this agreement, you must obtain permission in writing from +both the Project Gutenberg Literary Archive Foundation and Michael +Hart, the owner of the Project Gutenberg-tm trademark. Contact the +Foundation as set forth in Section 3 below. + +1.F. + +1.F.1. Project Gutenberg volunteers and employees expend considerable +effort to identify, do copyright research on, transcribe and proofread +public domain works in creating the Project Gutenberg-tm +collection. Despite these efforts, Project Gutenberg-tm electronic +works, and the medium on which they may be stored, may contain +“Defects,” such as, but not limited to, incomplete, inaccurate or +corrupt data, transcription errors, a copyright or other intellectual +property infringement, a defective or damaged disk or other medium, a +computer virus, or computer codes that damage or cannot be read by +your equipment. + +1.F.2. LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the “Right +of Replacement or Refund” described in paragraph 1.F.3, the Project +Gutenberg Literary Archive Foundation, the owner of the Project +Gutenberg-tm trademark, and any other party distributing a Project +Gutenberg-tm electronic work under this agreement, disclaim all +liability to you for damages, costs and expenses, including legal +fees. YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT +LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE +PROVIDED IN PARAGRAPH F3. YOU AGREE THAT THE FOUNDATION, THE +TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE +LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR +INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH +DAMAGE. + +1.F.3. LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a +defect in this electronic work within 90 days of receiving it, you can +receive a refund of the money (if any) you paid for it by sending a +written explanation to the person you received the work from. If you +received the work on a physical medium, you must return the medium with +your written explanation. The person or entity that provided you with +the defective work may elect to provide a replacement copy in lieu of a +refund. If you received the work electronically, the person or entity +providing it to you may choose to give you a second opportunity to +receive the work electronically in lieu of a refund. If the second copy +is also defective, you may demand a refund in writing without further +opportunities to fix the problem. + +1.F.4. Except for the limited right of replacement or refund set forth +in paragraph 1.F.3, this work is provided to you ‘AS-IS’ WITH NO OTHER +WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +WARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE. + +1.F.5. Some states do not allow disclaimers of certain implied +warranties or the exclusion or limitation of certain types of damages. +If any disclaimer or limitation set forth in this agreement violates the +law of the state applicable to this agreement, the agreement shall be +interpreted to make the maximum disclaimer or limitation permitted by +the applicable state law. The invalidity or unenforceability of any +provision of this agreement shall not void the remaining provisions. + +1.F.6. INDEMNITY - You agree to indemnify and hold the Foundation, the +trademark owner, any agent or employee of the Foundation, anyone +providing copies of Project Gutenberg-tm electronic works in accordance +with this agreement, and any volunteers associated with the production, +promotion and distribution of Project Gutenberg-tm electronic works, +harmless from all liability, costs and expenses, including legal fees, +that arise directly or indirectly from any of the following which you do +or cause to occur: (a) distribution of this or any Project Gutenberg-tm +work, (b) alteration, modification, or additions or deletions to any +Project Gutenberg-tm work, and (c) any Defect you cause. + + +Section 2. Information about the Mission of Project Gutenberg-tm + +Project Gutenberg-tm is synonymous with the free distribution of +electronic works in formats readable by the widest variety of computers +including obsolete, old, middle-aged and new computers. It exists +because of the efforts of hundreds of volunteers and donations from +people in all walks of life. + +Volunteers and financial support to provide volunteers with the +assistance they need, is critical to reaching Project Gutenberg-tm’s +goals and ensuring that the Project Gutenberg-tm collection will +remain freely available for generations to come. In 2001, the Project +Gutenberg Literary Archive Foundation was created to provide a secure +and permanent future for Project Gutenberg-tm and future generations. +To learn more about the Project Gutenberg Literary Archive Foundation +and how your efforts and donations can help, see Sections 3 and 4 +and the Foundation web page at http://www.pglaf.org. + + +Section 3. Information about the Project Gutenberg Literary Archive +Foundation + +The Project Gutenberg Literary Archive Foundation is a non profit +501(c)(3) educational corporation organized under the laws of the +state of Mississippi and granted tax exempt status by the Internal +Revenue Service. The Foundation’s EIN or federal tax identification +number is 64-6221541. Its 501(c)(3) letter is posted at +http://pglaf.org/fundraising. Contributions to the Project Gutenberg +Literary Archive Foundation are tax deductible to the full extent +permitted by U.S. federal laws and your state’s laws. + +The Foundation’s principal office is located at 4557 Melan Dr. S. +Fairbanks, AK, 99712., but its volunteers and employees are scattered +throughout numerous locations. Its business office is located at +809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email +business@pglaf.org. Email contact links and up to date contact +information can be found at the Foundation’s web site and official +page at http://pglaf.org + +For additional contact information: + Dr. Gregory B. Newby + Chief Executive and Director + gbnewby@pglaf.org + + +Section 4. Information about Donations to the Project Gutenberg +Literary Archive Foundation + +Project Gutenberg-tm depends upon and cannot survive without wide +spread public support and donations to carry out its mission of +increasing the number of public domain and licensed works that can be +freely distributed in machine readable form accessible by the widest +array of equipment including outdated equipment. Many small donations +($1 to $5,000) are particularly important to maintaining tax exempt +status with the IRS. + +The Foundation is committed to complying with the laws regulating +charities and charitable donations in all 50 states of the United +States. Compliance requirements are not uniform and it takes a +considerable effort, much paperwork and many fees to meet and keep up +with these requirements. We do not solicit donations in locations +where we have not received written confirmation of compliance. To +SEND DONATIONS or determine the status of compliance for any +particular state visit http://pglaf.org + +While we cannot and do not solicit contributions from states where we +have not met the solicitation requirements, we know of no prohibition +against accepting unsolicited donations from donors in such states who +approach us with offers to donate. + +International donations are gratefully accepted, but we cannot make +any statements concerning tax treatment of donations received from +outside the United States. U.S. laws alone swamp our small staff. + +Please check the Project Gutenberg Web pages for current donation +methods and addresses. Donations are accepted in a number of other +ways including checks, online payments and credit card donations. +To donate, please visit: http://pglaf.org/donate + + +Section 5. General Information About Project Gutenberg-tm electronic +works. + +Professor Michael S. Hart is the originator of the Project Gutenberg-tm +concept of a library of electronic works that could be freely shared +with anyone. For thirty years, he produced and distributed Project +Gutenberg-tm eBooks with only a loose network of volunteer support. + + +Project Gutenberg-tm eBooks are often created from several printed +editions, all of which are confirmed as Public Domain in the U.S. +unless a copyright notice is included. Thus, we do not necessarily +keep eBooks in compliance with any particular paper edition. + + +Most people start at our Web site which has the main PG search facility: + + http://www.gutenberg.org + +This Web site includes information about Project Gutenberg-tm, +including how to make donations to the Project Gutenberg Literary +Archive Foundation, how to help produce our new eBooks, and how to +subscribe to our email newsletter to hear about new eBooks. diff --git a/src/test/resources/UTF-8-test.txt b/src/test/resources/UTF-8-test.txt new file mode 100644 index 0000000000000000000000000000000000000000..a5b5d50e6b61eb9a3b751b3954f83e61bb59db9b GIT binary patch literal 22781 zcmdU1X_Fh*b>CYjiIrHEbIyIeVd1bb#8j*y4MbNJE8ngr{T%> zH=?M0*NGF|Zns=*maBHFY*)*j-4j3B+Sy$_dEy6TNmiFvPA)BPEUj+fmviUj>}zDb zylhzyHeB;;sk==Fw0Y8Snr+$lJK|ijTdwCUO2hB+4}n}98At=QQVnO2tbk<@(9!SA*KTMb-()7zJ}9Yk)m)3ovMPE_?< z4&m`=AYfdi2gL9x(zW;T2&>e#!>~kZOLg0AmhhcCgBHcvb3FXf@9Z>qKS;P_9xq{M zX9r^v+X4}FC$KAEXd?8A)3Eozr9kXRLJ%VDrme$0ABAGOEs(JYzJ!rugIk~^6$2>n zcEzq>1dFOMR23y23`tPI^(hkaJx~+w1GHs>5#U|33BkDdJ8tNBov>p!@zi!F0^_$X zwVlAF6hNWW!L)ctp%pbicFV`;>TVE5OOcBa*d6d8P>GKyMu;y#v2jP!v2ly zk#^vNEf_7C=(s`3piW3-P;OK9WvZqTur#5BA;#K-8dm*1m=Jj3)$CxO%20DT9xyLe zwsDmL*Fxz!%_e`wy4l5ZQiI8()+YdS1n&OdNLR+dk#pITbEYjt^fc~L?miBLAnr&X@2<+Unk z1xxnas$;=~9Ct4@5j@+*5Y%*_V9+xuWkXKLj~OeW4FN%?jpW=YyARqX)rK9|RoI{z z6*L^m6}lKPRobrk?FftsJC)FhN~FyyrMJYKuwgkgxUNh6oN`}GFDwzt;8txQTCib0 zpaX3vNd^8-vO$_rm*s*-zPDPw2N~g;P^31r#;!I1WQ{2rZPN#c8k3rQ#uD?@lrel9 zc&guQIdCZHh-Bg7@$K#H&0~wwZMb+&)Ax7DrV9Ahba&}X@N9^jV}PH6M+Px?o*Hkbs)2c49sb4l;8pIYPlARu zoGPT(Z8+q>K>~n^Evy`)jv=&wc~r>Af(RF7VRiKHP%*^JY6M2Y=RqsH9fW|J=p5L9 znCI@cwPR{0b+~K4%VEB*<45q|vDS%Qr|sIF1&&Z+V?K-_EmasQ z8kByYLD(%L0K%By=V1!rzE^DozUTgkfsVl1(8MKiTZq~&WQ?&SPutJLna1mUxDM_8 zP)ZNpy(L@sZI)m$I*2Ai09`mdo-eM(DM*@X6gpHP8)DPgmBh+wV22I*AB0J!9+fy; z8{>>!u-$}rV^pxJHiRsQ=#+ES2`J?=&=eCklnU@e6AC)5L_(+U%sfB{lLwa<+D)c0 z@KC(hsyj~Ym{ISVi82sR3hY2obbeCmm0&TKs!kix5mfO0s0&XAKVcanR?92p6&3Aj z8SfK619~GH1bV)=)P}u8YO7}+ICuU$#}Wm8Gqf%qN3>YhoB8(iHYkdjXatJ~CgW3j zY>f4iQ(Nk~HBd)WUqlpu?vm2{m{U#~UP-4-nIT8$v^)WO76SEQ@N+P=V`Y;of&qRv z)cla%oCy!KxVqci&#Y-mCc=zBS!*Ss((AM;P-3Qwi3bb$e1=vyB2^fTxfonxrVgS2 zoKKuoB7(+ABNaY2vzm!32iYF<<33-#7gY~A0sMvCghhF zq-e|{8C-$Ugm&8k%c>);(`u^J2$kppjT%GKyf1X4j@nzOxnwR`JcchcJNi(NkUAXU zsBwD@$v}mutT zh$~}5e&&(OGeVveIhlv#Txx{K7vFe!R?Jf(r*fF<3Yq8RORop!41|tK>vn&mbRg_| zm`Ke*yX7d<8wxo^y-#oF)cePZYNK$x)71O)b{_BPK~X?uV0lu#G3m<#4hJ(Lj;r?y z0rQC89Lx$irrv9cn1}opAZH|VRK3AOy>gQIHZf-ayJ7t0~3rOy}M?jxyqUWGiivnIv=ZUUoW)yJ@%RT>A zZ|6_n7AGs61L#Y#T@(gG2L*alV=(oPCW|Yqyd_s}%50DMoQs02-}TDL_lo|U|9IgU zC=ru;a?(B`3|YRkv3!@5islZ`Ka<&Mu%8qz@G5oS74LhKL_mwUeB`g-0 z885ud+RgBaa?Su6Jh+(ko8rwxp}|7{J$ZAyStvAkr{x|eJ@}@Vx<*jdf`#awGu6MuZz3+SfqlZB<_ZYm#KJdX0efT3E z{n*Dp@ySnp`tc{O+%Dc{KKqNm^vj?7mHw}O{tLhM#V4=MWD%p!p1(ZSbgF~$IZ9|@ zg!S8ed2saM1%5jc`snXjU|w4_tD$k&MOa#0(%M{7dBDVc`BIg7T&ttBKjlj4(;A;C z%OM8p(pcEW0&zC_v`#PuCjC>2m;UAU-Y8z_ntVyVEMJkY%CF0B$ZyJT$#2W=$nVPU z$?wZk^0gTrVd(n<`9t|5`D6JL`BV8b`E&UT`AhjL`D^(b`CIurd0L7ms`DQ&^p6@y z`L3KAm+#sX`8t%Z^NN=5b@{sdz5Ijxqx_Tnv;2$vtNfe%yZndzr`(Wl$ngJN9>_N+ zkBxD0tWOa~o#LpUo^Ebg{P?c6w`b&8c}|{}7v#U>zvY`V@PFJCkH$WbuTdWB&64GgT zysDZJJZ*k%+6;6}KSAQkmiF_oRc4|oB7=iG(#2q13uZ9^c@>u(QBsJ|L8XgMXgXOq zm#rG3WWd$cxU&sS8R<2D{;i>eh5TVo7);F63rof4=zI7naP@a<-sQ;!47FNLX26 z$t>m86H2;}Fo~I(<6PcQ(on~9X!b$vkUblwOT$G!=a?r8i#%}tK7u}$E(?52e5$%z z*gDHJG1+X$Lc>MgN8*O8*Ce66mo|5>-H8DsEtVFV6DByx&-srR$fax7FT;7z+SNRt z9uA?!e{cTuJqZ2D`v2HZW++5+qqcUo+YTB| z5%$vF(4+}4($33K3g=HB_{TU{ceuFp^0>{9KK<6e(WXyw-d)#qrSHrK4`C4W@n1a# ze);9^9145>{DTL(fKE5hGUG_Lmbq~a<|`Q&!6_2p=}QBWMm2ea0_}P(FGrd?Dc08U2^rTakV#X z(Xa@!XT)ZkA}VQUq(i@Jt;5YC4X0I>Gg1m|sD$YQ%kbjn(^BG&1Lw8KC-o-N)2xl) z*{Cy)XwN6?Gk^8aUTcTgmT;Fg4`YW4w{vK-y7n%!r@hIhc*HIwuXW+JhA~|rCz7kD zn_I9`2oX=0$j~ z30Q8Z3fn^)s82;V(zUh!)zHLRu!j}ZoYp3d(oJZC#sC`E$OSyN?QmdVhS@=Y=}{-( zrZe5oLhIafo$g}7J)};MR=h6_ACB8crtjR2W%^xdvJl5O|J8_AObq4NWVKM1)X&S53})42gHvR~Io zCU5;QAF6wAcnkNvJ|GL31u)a|b4=={0}XCp4!onNK0W z_(((4S{kxYI#EJ5wb`r%y)(juZ6z1&m-~8h>Gr6A7gfNC(|{@KZm0;o+q!9y_RIk$ z7vs^vE!Ql8fE`MnZe=scf6dnoFWAP%kJAe|{{-W{H~n<>H}jV-^bZ=y^2RC~gR0n} zkc;=A5NYAHD~0p8o<+db4A9Y@pgbl3uV0=OaVhN4#N5@hu%i?v24T zJ(F?mj%30|pjAJ?#2fmEhk#j-o4F^<;Vp>-Cod#(nAA=*Rwo7g`!3#-%}x${>ipI5 z!O2`BF>Lq(lPBm7t^@2UBe@rEL(U*LH;rLfodHTRS5xE6G8Hmm;>xXhvJ0b73K;#4*}HwR>dIZnepaCUAE6hTQO%}vGzUetHYUmh$4KVTMi zNfR2UFij8q9n>fC1Idl)ptnatpK)KC*w6+8W0gf`1F*Flj}by43 zC48);eEiH|X~d!}__&>n8g^^E;Ek(?MZ~f#5tC7yM2Vv=mLH8tX5`n~{)LiWmHTZs zfIPlSt;OW-$JJWE$W++IXh3%+^~WyXxV7MQ%qHF^n5l3w%v3jld22x#%-CXJgClgR zDUQ(j_@FSIvCSCFjLsA@r4yJ%=`>bD%O0UqReglcCyMja*iDR?(V1eVbON&|oyKzL zFGlE8s1Z7^6z3;;9sm|&W^|^QDV@M9Lg#Ip;0OTqXie_JvV5|DfU-W8I1Zch%s+aK zW;-RE7Lt;R(lwWfN<5k-*n@yrRL~=$aGDH@-g?Z^*m(16PpHNsaT!^{^P4=S4s$u4 zn4_g-PG6ET$^-GbU5eSWO3BPePsM`!RsujH>i4x$Ilc89&MIe9-DdQnA3T!WrWLlMIw zp{lN#KWL=_YYXWDsV)UqntpW`df>;3Z59c&tui(!b9f~fie^igAJ#+iN8yc(*PM%o zgQ+lQ#JPjq$ur$Tjq@=p!HGQLh0Y9+wa(;}L%?JP$Ax_^nZjg87@pla_1*mCaesQs zBt{CNc)%f4raCGP@*pJw5#o&C&kib4X2*-!d*E^m64IZa^19wsZbc|NX=Mqr?7V z!~O?`{SOZN9~$;QJnVmD*#GFT|FL2J|ZGz&1f`h z_Eg0#9_P<(SztEeVN5IK^2&0qE&E{3&!4{dd8KC_9bCn0@XQtbyn>$t{6x|G*@p*L z@ftjf&+!^Oi_h^IJd4lq8a#*3@fsXVxA`=q{yCuGHFyqacnzKd8eW6vfri)Md3=u7 p;CXzG*Wh`4j@JOEDqJmkvO@N>BDCH5J3T8tzwg#xE%c3^^?x0SNE-kE literal 0 HcmV?d00001