Import atomix/{storage,utils} 70/104670/1
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 1 Mar 2023 22:47:19 +0000 (23:47 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 1 Mar 2023 22:57:01 +0000 (23:57 +0100)
This is an import of our upstream dependency, atomix-storage, along with
its dependency, atomix-utils.

The atomix.io team has decided to abandon their Java code and archived
it under https://github.com/atomix/atomix-archive.

As per https://github.com/atomix/atomix-archive/blob/master/LICENSE,
this code is licensed under Apache License 2.0, which is compatible with
Eclipse Public License 1.0, which we are using.

This code drop imports the source code for the two artifacts we use, as
of their latest available state as available at
https://github.com/atomix/atomix-archive/tree/5c57662f8be16d3448c4c9cac2e2bec9317c4300
in verbatim, without any changes or actual build integration.

JIRA: CONTROLLER-2069
Change-Id: I873779dba9eac350e92e1c4dae69a79da43d3068
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
212 files changed:
third-party/atomix/storage/pom.xml [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/StorageException.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/StorageLevel.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/AbstractBuffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/AbstractBytes.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/Buffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferAllocator.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferInput.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferOutput.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferPool.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/ByteBufferBuffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/ByteBufferBytes.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/Bytes.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BytesInput.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BytesOutput.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/DirectBuffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/DirectBytes.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/FileBuffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/FileBytes.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/HeapBuffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/HeapBytes.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/MappedBuffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/MappedBytes.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/PooledAllocator.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/ReadOnlyBuffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/SlicedBuffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/SwappedBuffer.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/SwappedBytes.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/UnpooledAllocator.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/UnpooledDirectAllocator.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/UnpooledHeapAllocator.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/WrappedBytes.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/package-info.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/DelegatingJournal.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/DelegatingJournalReader.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/DelegatingJournalWriter.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/FileChannelJournalSegmentReader.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/FileChannelJournalSegmentWriter.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/Indexed.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/Journal.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalReader.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalSegment.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalSegmentDescriptor.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalSegmentFile.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalWriter.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappableJournalSegmentReader.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappableJournalSegmentWriter.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappedJournalSegmentReader.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappedJournalSegmentWriter.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/SegmentedJournal.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/SegmentedJournalReader.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/SegmentedJournalWriter.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/JournalIndex.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/Position.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/SparseJournalIndex.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/package-info.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/journal/package-info.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/package-info.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/statistics/StorageStatistics.java [new file with mode: 0644]
third-party/atomix/storage/src/main/java/io/atomix/storage/statistics/package-info.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/BufferTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/DirectBufferTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/FileBufferTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/FileTesting.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/HeapBufferTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/MappedBufferTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/journal/AbstractJournalTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/journal/DiskJournalTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/journal/JournalSegmentDescriptorTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/journal/JournalSegmentFileTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/journal/MappedJournalTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/journal/MemoryJournalTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/journal/PersistentJournalTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/journal/TestEntry.java [new file with mode: 0644]
third-party/atomix/storage/src/test/java/io/atomix/storage/journal/index/SparseJournalIndexTest.java [new file with mode: 0644]
third-party/atomix/storage/src/test/resources/logback.xml [new file with mode: 0644]
third-party/atomix/utils/pom.xml [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/AbstractIdentifier.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/AbstractNamed.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/AtomixIOException.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/AtomixRuntimeException.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/Builder.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/ConfiguredType.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/Generics.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/Identifier.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/Managed.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/Named.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/NamedType.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/ServiceException.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/Type.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/Version.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AbstractAccumulator.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AbstractThreadContext.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Accumulator.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AtomixFuture.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AtomixThread.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AtomixThreadFactory.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareSingleThreadContext.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareSingleThreadContextFactory.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareThreadPoolContext.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareThreadPoolContextFactory.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ComposableFuture.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Futures.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/NullThreadContext.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/OrderedExecutor.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/OrderedFuture.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferenceCounted.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferenceFactory.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferenceManager.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferencePool.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Retries.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/RetryingFunction.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Scheduled.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Scheduler.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/SingleThreadContext.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadContext.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadContextFactory.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadModel.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadPoolContext.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Threads.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/config/Config.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/config/ConfigMapper.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/config/ConfigurationException.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/config/Configured.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/config/NamedConfig.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/config/TypedConfig.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/config/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/event/AbstractEvent.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/event/AbstractListenerManager.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/event/Event.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/event/EventFilter.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/event/EventListener.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/event/EventSink.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/event/ListenerRegistry.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/event/ListenerService.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/event/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/logging/ContextualLogger.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/logging/ContextualLoggerFactory.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/logging/DelegatingLogger.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/logging/LoggerContext.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/logging/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/memory/BufferCleaner.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/memory/Cleaner.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MappedMemory.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MappedMemoryAllocator.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/memory/Memory.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MemoryAllocator.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MemorySize.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/memory/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/misc/ArraySizeHashPrinter.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/misc/Match.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/misc/SlidingWindowCounter.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/misc/StringUtils.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/misc/TimestampPrinter.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/misc/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/net/Address.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/net/MalformedAddressException.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/net/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/BufferAwareByteArrayOutputStream.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/ByteArrayOutput.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/KryoIOPool.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/KryoInputPool.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/KryoOutputPool.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/Namespace.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/NamespaceConfig.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/NamespaceTypeConfig.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/Namespaces.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/Serializer.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/SerializerBuilder.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ArraysAsListSerializer.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/DefaultSerializers.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ImmutableListSerializer.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ImmutableMapSerializer.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ImmutableSetSerializer.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/Clock.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/Epoch.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/LogicalClock.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/LogicalTimestamp.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/MultiValuedTimestamp.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/Timestamp.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/VectorClock.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/VectorTimestamp.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/Version.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/Versioned.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/WallClock.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/WallClockTimestamp.java [new file with mode: 0644]
third-party/atomix/utils/src/main/java/io/atomix/utils/time/package-info.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/ArraySizeHashPrinterTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/GenericsTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/MatchTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/TimestampPrinterTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/VersionTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/concurrent/OrderedFutureTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/concurrent/RetryingFunctionTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/logging/LoggerContextTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/misc/StringUtilsTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/net/AddressTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/serializer/BufferAwareByteArrayOutputStreamTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/serializer/KryoInputPoolTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/serializer/KryoOutputPoolTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/time/EpochTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/time/LogicalClockTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/time/LogicalTimestampTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/time/MultiValuedTimestampTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/time/VersionTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/time/VersionedTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/time/WallClockTest.java [new file with mode: 0644]
third-party/atomix/utils/src/test/java/io/atomix/utils/time/WallClockTimestampTest.java [new file with mode: 0644]

diff --git a/third-party/atomix/storage/pom.xml b/third-party/atomix/storage/pom.xml
new file mode 100644 (file)
index 0000000..326c021
--- /dev/null
@@ -0,0 +1,56 @@
+<!--
+  ~ Copyright 2017-present Open Networking Foundation
+  ~
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>io.atomix</groupId>
+    <artifactId>atomix-parent</artifactId>
+    <version>3.2.0-SNAPSHOT</version>
+  </parent>
+
+  <packaging>bundle</packaging>
+  <artifactId>atomix-storage</artifactId>
+  <name>Atomix Storage</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>io.atomix</groupId>
+      <artifactId>atomix-utils</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Export-Package>
+              io.atomix.storage.*
+            </Export-Package>
+            <Import-Package>
+              !sun.nio.ch,!sun.misc,*
+            </Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/StorageException.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/StorageException.java
new file mode 100644 (file)
index 0000000..46333ff
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage;
+
+/**
+ * Log exception.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class StorageException extends RuntimeException {
+
+  public StorageException() {
+  }
+
+  public StorageException(String message) {
+    super(message);
+  }
+
+  public StorageException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public StorageException(Throwable cause) {
+    super(cause);
+  }
+
+  /**
+   * Exception thrown when an entry being stored is too large.
+   */
+  public static class TooLarge extends StorageException {
+    public TooLarge(String message) {
+      super(message);
+    }
+  }
+
+  /**
+   * Exception thrown when storage runs out of disk space.
+   */
+  public static class OutOfDiskSpace extends StorageException {
+    public OutOfDiskSpace(String message) {
+      super(message);
+    }
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/StorageLevel.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/StorageLevel.java
new file mode 100644 (file)
index 0000000..7fe3e36
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage;
+
+/**
+ * Storage level configuration values which control how logs are stored on disk or in memory.
+ */
+public enum StorageLevel {
+
+  /**
+   * Stores data in memory only.
+   */
+  @Deprecated
+  MEMORY,
+
+  /**
+   * Stores data in a memory-mapped file.
+   */
+  MAPPED,
+
+  /**
+   * Stores data on disk.
+   */
+  DISK
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/AbstractBuffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/AbstractBuffer.java
new file mode 100644 (file)
index 0000000..5a86e40
--- /dev/null
@@ -0,0 +1,946 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.concurrent.ReferenceManager;
+import io.atomix.utils.memory.Memory;
+
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteOrder;
+import java.nio.InvalidMarkException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static io.atomix.storage.buffer.Bytes.BOOLEAN;
+import static io.atomix.storage.buffer.Bytes.BYTE;
+import static io.atomix.storage.buffer.Bytes.SHORT;
+
+/**
+ * Abstract buffer implementation.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public abstract class AbstractBuffer implements Buffer {
+  static final int DEFAULT_INITIAL_CAPACITY = 4096;
+  static final int MAX_SIZE = Integer.MAX_VALUE - 5;
+
+  protected final Bytes bytes;
+  private int offset;
+  private int initialCapacity;
+  private int capacity;
+  private int maxCapacity;
+  private int position;
+  private int limit = -1;
+  private int mark = -1;
+  private final AtomicInteger references = new AtomicInteger();
+  protected final ReferenceManager<Buffer> referenceManager;
+  private SwappedBuffer swap;
+
+  protected AbstractBuffer(Bytes bytes, ReferenceManager<Buffer> referenceManager) {
+    this(bytes, 0, 0, 0, referenceManager);
+  }
+
+  protected AbstractBuffer(Bytes bytes, int offset, int initialCapacity, int maxCapacity, ReferenceManager<Buffer> referenceManager) {
+    if (bytes == null) {
+      throw new NullPointerException("bytes cannot be null");
+    }
+    if (offset < 0) {
+      throw new IndexOutOfBoundsException("offset out of bounds of the underlying byte array");
+    }
+    this.bytes = bytes;
+    this.offset = offset;
+    this.capacity = 0;
+    this.initialCapacity = initialCapacity;
+    this.maxCapacity = maxCapacity;
+    capacity(initialCapacity);
+    this.referenceManager = referenceManager;
+    references.set(1);
+  }
+
+  /**
+   * Resets the buffer's internal offset and capacity.
+   */
+  protected AbstractBuffer reset(int offset, int capacity, int maxCapacity) {
+    this.offset = offset;
+    this.capacity = 0;
+    this.initialCapacity = capacity;
+    this.maxCapacity = maxCapacity;
+    capacity(initialCapacity);
+    references.set(0);
+    rewind();
+    return this;
+  }
+
+  @Override
+  public Buffer acquire() {
+    references.incrementAndGet();
+    return this;
+  }
+
+  @Override
+  public boolean release() {
+    if (references.decrementAndGet() == 0) {
+      if (referenceManager != null) {
+        referenceManager.release(this);
+      } else {
+        bytes.close();
+      }
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public int references() {
+    return references.get();
+  }
+
+  @Override
+  public Bytes bytes() {
+    return bytes;
+  }
+
+  @Override
+  public ByteOrder order() {
+    return bytes.order();
+  }
+
+  @Override
+  public Buffer order(ByteOrder order) {
+    if (order == null) {
+      throw new NullPointerException("order cannot be null");
+    }
+    if (order == order()) {
+      return this;
+    }
+    if (swap != null) {
+      return swap;
+    }
+    swap = new SwappedBuffer(this, offset, capacity, maxCapacity, referenceManager);
+    return swap;
+  }
+
+  @Override
+  public boolean isDirect() {
+    return bytes.isDirect();
+  }
+
+  @Override
+  public boolean isFile() {
+    return bytes.isFile();
+  }
+
+  @Override
+  public boolean isReadOnly() {
+    return false;
+  }
+
+  @Override
+  public Buffer asReadOnlyBuffer() {
+    return new ReadOnlyBuffer(this, referenceManager)
+        .reset(offset, capacity, maxCapacity)
+        .position(position)
+        .limit(limit);
+  }
+
+  @Override
+  public Buffer compact() {
+    compact(offset(position), offset, (limit != -1 ? limit : capacity) - offset(position));
+    return clear();
+  }
+
+  /**
+   * Compacts the given bytes.
+   */
+  protected abstract void compact(int from, int to, int length);
+
+  @Override
+  public Buffer slice() {
+    int maxCapacity = this.maxCapacity - position;
+    int capacity = Math.min(Math.min(initialCapacity, maxCapacity), bytes.size() - offset(position));
+    if (limit != -1) {
+      capacity = maxCapacity = limit - position;
+    }
+    return new SlicedBuffer(this, bytes, offset(position), capacity, maxCapacity);
+  }
+
+  @Override
+  public Buffer slice(int length) {
+    checkSlice(position, length);
+    return new SlicedBuffer(this, bytes, offset(position), length, length);
+  }
+
+  @Override
+  public Buffer slice(int offset, int length) {
+    checkSlice(offset, length);
+    return new SlicedBuffer(this, bytes, offset(offset), length, length);
+  }
+
+  @Override
+  public int offset() {
+    return offset;
+  }
+
+  @Override
+  public int capacity() {
+    return capacity;
+  }
+
+  /**
+   * Updates the buffer capacity.
+   */
+  public Buffer capacity(int capacity) {
+    if (capacity > maxCapacity) {
+      throw new IllegalArgumentException("capacity cannot be greater than maximum capacity");
+    } else if (capacity < this.capacity) {
+      throw new IllegalArgumentException("capacity cannot be decreased");
+    } else if (capacity != this.capacity) {
+      // It's possible that the bytes could already meet the requirements of the capacity.
+      if (offset(capacity) > bytes.size()) {
+        bytes.resize((int) Math.min(Memory.Util.toPow2(offset(capacity)), Integer.MAX_VALUE));
+      }
+      this.capacity = capacity;
+    }
+    return this;
+  }
+
+  @Override
+  public int maxCapacity() {
+    return maxCapacity;
+  }
+
+  @Override
+  public int position() {
+    return position;
+  }
+
+  @Override
+  public Buffer position(int position) {
+    if (limit != -1 && position > limit) {
+      throw new IllegalArgumentException("position cannot be greater than limit");
+    } else if (limit == -1 && position > maxCapacity) {
+      throw new IllegalArgumentException("position cannot be greater than capacity");
+    }
+    if (position > capacity) {
+      capacity((int) Math.min(maxCapacity, Memory.Util.toPow2(position)));
+    }
+    this.position = position;
+    return this;
+  }
+
+  /**
+   * Returns the real offset of the given relative offset.
+   */
+  private int offset(int offset) {
+    return this.offset + offset;
+  }
+
+  @Override
+  public int limit() {
+    return limit;
+  }
+
+  @Override
+  public Buffer limit(int limit) {
+    if (limit > maxCapacity) {
+      throw new IllegalArgumentException("limit cannot be greater than buffer capacity");
+    }
+    if (limit < -1) {
+      throw new IllegalArgumentException("limit cannot be negative");
+    }
+    if (limit != -1 && offset(limit) > bytes.size()) {
+      bytes.resize(offset(limit));
+    }
+    this.limit = limit;
+    return this;
+  }
+
+  @Override
+  public int remaining() {
+    return (limit == -1 ? maxCapacity : limit) - position;
+  }
+
+  @Override
+  public boolean hasRemaining() {
+    return remaining() > 0;
+  }
+
+  @Override
+  public Buffer flip() {
+    limit = position;
+    position = 0;
+    mark = -1;
+    return this;
+  }
+
+  @Override
+  public Buffer mark() {
+    this.mark = position;
+    return this;
+  }
+
+  @Override
+  public Buffer rewind() {
+    position = 0;
+    mark = -1;
+    return this;
+  }
+
+  @Override
+  public Buffer reset() {
+    if (mark == -1) {
+      throw new InvalidMarkException();
+    }
+    position = mark;
+    return this;
+  }
+
+  @Override
+  public Buffer skip(int length) {
+    if (length > remaining()) {
+      throw new IndexOutOfBoundsException("length cannot be greater than remaining bytes in the buffer");
+    }
+    position += length;
+    return this;
+  }
+
+  @Override
+  public Buffer clear() {
+    position = 0;
+    limit = -1;
+    mark = -1;
+    return this;
+  }
+
+  /**
+   * Checks that the offset is within the bounds of the buffer.
+   */
+  protected void checkOffset(int offset) {
+    if (offset(offset) < this.offset) {
+      throw new IndexOutOfBoundsException();
+    } else if (limit == -1) {
+      if (offset > maxCapacity) {
+        throw new IndexOutOfBoundsException();
+      }
+    } else {
+      if (offset > limit) {
+        throw new IndexOutOfBoundsException();
+      }
+    }
+  }
+
+  /**
+   * Checks bounds for a slice.
+   */
+  protected int checkSlice(int offset, int length) {
+    checkOffset(offset);
+    if (limit == -1) {
+      if (offset + length > capacity) {
+        if (capacity < maxCapacity) {
+          capacity(calculateCapacity(offset + length));
+        } else {
+          throw new BufferUnderflowException();
+        }
+      }
+    } else {
+      if (offset + length > limit) {
+        throw new BufferUnderflowException();
+      }
+    }
+    return offset(offset);
+  }
+
+  /**
+   * Checks bounds for a read for the given length.
+   */
+  protected int checkRead(int length) {
+    checkRead(position, length);
+    int previousPosition = this.position;
+    this.position = previousPosition + length;
+    return offset(previousPosition);
+  }
+
+  /**
+   * Checks bounds for a read.
+   */
+  protected int checkRead(int offset, int length) {
+    checkOffset(offset);
+    if (limit == -1) {
+      if (offset + length > capacity) {
+        if (capacity < maxCapacity) {
+          if (this.offset + offset + length <= bytes.size()) {
+            capacity = bytes.size() - this.offset;
+          } else {
+            capacity(calculateCapacity(offset + length));
+          }
+        } else {
+          throw new BufferUnderflowException();
+        }
+      }
+    } else {
+      if (offset + length > limit) {
+        throw new BufferUnderflowException();
+      }
+    }
+    return offset(offset);
+  }
+
+  /**
+   * Checks bounds for a write of the given length.
+   */
+  protected int checkWrite(int length) {
+    checkWrite(position, length);
+    int previousPosition = this.position;
+    this.position = previousPosition + length;
+    return offset(previousPosition);
+  }
+
+  /**
+   * Checks bounds for a write.
+   */
+  protected int checkWrite(int offset, int length) {
+    checkOffset(offset);
+    if (limit == -1) {
+      if (offset + length > capacity) {
+        if (capacity < maxCapacity) {
+          capacity(calculateCapacity(offset + length));
+        } else {
+          throw new BufferOverflowException();
+        }
+      }
+    } else {
+      if (offset + length > limit) {
+        throw new BufferOverflowException();
+      }
+    }
+    return offset(offset);
+  }
+
+  /**
+   * Calculates the next capacity that meets the given minimum capacity.
+   */
+  private int calculateCapacity(int minimumCapacity) {
+    int newCapacity = Math.min(Math.max(capacity, 2), minimumCapacity);
+    while (newCapacity < Math.min(minimumCapacity, maxCapacity)) {
+      newCapacity <<= 1;
+    }
+    return Math.min(newCapacity, maxCapacity);
+  }
+
+  @Override
+  public Buffer zero() {
+    bytes.zero(offset);
+    return this;
+  }
+
+  @Override
+  public Buffer zero(int offset) {
+    checkOffset(offset);
+    bytes.zero(offset(offset));
+    return this;
+  }
+
+  @Override
+  public Buffer zero(int offset, int length) {
+    checkOffset(offset);
+    bytes.zero(offset(offset), length);
+    return this;
+  }
+
+  @Override
+  public Buffer read(Buffer buffer) {
+    int length = Math.min(buffer.remaining(), remaining());
+    read(buffer.bytes(), buffer.offset() + buffer.position(), length);
+    buffer.position(buffer.position() + length);
+    return this;
+  }
+
+  @Override
+  public Buffer read(Bytes bytes) {
+    this.bytes.read(checkRead(bytes.size()), bytes, 0, bytes.size());
+    return this;
+  }
+
+  @Override
+  public Buffer read(Bytes bytes, int offset, int length) {
+    this.bytes.read(checkRead(length), bytes, offset, length);
+    return this;
+  }
+
+  @Override
+  public Buffer read(int srcOffset, Bytes bytes, int dstOffset, int length) {
+    this.bytes.read(checkRead(srcOffset, length), bytes, dstOffset, length);
+    return this;
+  }
+
+  @Override
+  public Buffer read(byte[] bytes) {
+    this.bytes.read(checkRead(bytes.length), bytes, 0, bytes.length);
+    return this;
+  }
+
+  @Override
+  public Buffer read(byte[] bytes, int offset, int length) {
+    this.bytes.read(checkRead(length), bytes, offset, length);
+    return this;
+  }
+
+  @Override
+  public Buffer read(int srcOffset, byte[] bytes, int dstOffset, int length) {
+    this.bytes.read(checkRead(srcOffset, length), bytes, dstOffset, length);
+    return this;
+  }
+
+  @Override
+  public int readByte() {
+    return bytes.readByte(checkRead(BYTE));
+  }
+
+  @Override
+  public int readByte(int offset) {
+    return bytes.readByte(checkRead(offset, BYTE));
+  }
+
+  @Override
+  public int readUnsignedByte() {
+    return bytes.readUnsignedByte(checkRead(BYTE));
+  }
+
+  @Override
+  public int readUnsignedByte(int offset) {
+    return bytes.readUnsignedByte(checkRead(offset, BYTE));
+  }
+
+  @Override
+  public char readChar() {
+    return bytes.readChar(checkRead(Bytes.CHARACTER));
+  }
+
+  @Override
+  public char readChar(int offset) {
+    return bytes.readChar(checkRead(offset, Bytes.CHARACTER));
+  }
+
+  @Override
+  public short readShort() {
+    return bytes.readShort(checkRead(SHORT));
+  }
+
+  @Override
+  public short readShort(int offset) {
+    return bytes.readShort(checkRead(offset, SHORT));
+  }
+
+  @Override
+  public int readUnsignedShort() {
+    return bytes.readUnsignedShort(checkRead(SHORT));
+  }
+
+  @Override
+  public int readUnsignedShort(int offset) {
+    return bytes.readUnsignedShort(checkRead(offset, SHORT));
+  }
+
+  @Override
+  public int readMedium() {
+    return bytes.readMedium(checkRead(3));
+  }
+
+  @Override
+  public int readMedium(int offset) {
+    return bytes.readMedium(checkRead(offset, 3));
+  }
+
+  @Override
+  public int readUnsignedMedium() {
+    return bytes.readUnsignedMedium(checkRead(3));
+  }
+
+  @Override
+  public int readUnsignedMedium(int offset) {
+    return bytes.readUnsignedMedium(checkRead(offset, 3));
+  }
+
+  @Override
+  public int readInt() {
+    return bytes.readInt(checkRead(Bytes.INTEGER));
+  }
+
+  @Override
+  public int readInt(int offset) {
+    return bytes.readInt(checkRead(offset, Bytes.INTEGER));
+  }
+
+  @Override
+  public long readUnsignedInt() {
+    return bytes.readUnsignedInt(checkRead(Bytes.INTEGER));
+  }
+
+  @Override
+  public long readUnsignedInt(int offset) {
+    return bytes.readUnsignedInt(checkRead(offset, Bytes.INTEGER));
+  }
+
+  @Override
+  public long readLong() {
+    return bytes.readLong(checkRead(Bytes.LONG));
+  }
+
+  @Override
+  public long readLong(int offset) {
+    return bytes.readLong(checkRead(offset, Bytes.LONG));
+  }
+
+  @Override
+  public float readFloat() {
+    return bytes.readFloat(checkRead(Bytes.FLOAT));
+  }
+
+  @Override
+  public float readFloat(int offset) {
+    return bytes.readFloat(checkRead(offset, Bytes.FLOAT));
+  }
+
+  @Override
+  public double readDouble() {
+    return bytes.readDouble(checkRead(Bytes.DOUBLE));
+  }
+
+  @Override
+  public double readDouble(int offset) {
+    return bytes.readDouble(checkRead(offset, Bytes.DOUBLE));
+  }
+
+  @Override
+  public boolean readBoolean() {
+    return bytes.readBoolean(checkRead(BYTE));
+  }
+
+  @Override
+  public boolean readBoolean(int offset) {
+    return bytes.readBoolean(checkRead(offset, BYTE));
+  }
+
+  @Override
+  public String readString(Charset charset) {
+    if (readBoolean(position)) {
+      byte[] bytes = new byte[readUnsignedShort(position + BOOLEAN)];
+      read(position + BOOLEAN + SHORT, bytes, 0, bytes.length);
+      this.position += BOOLEAN + SHORT + bytes.length;
+      return new String(bytes, charset);
+    } else {
+      this.position += BOOLEAN;
+    }
+    return null;
+  }
+
+  @Override
+  public String readString(int offset, Charset charset) {
+    if (readBoolean(offset)) {
+      byte[] bytes = new byte[readUnsignedShort(offset + BOOLEAN)];
+      read(offset + BOOLEAN + SHORT, bytes, 0, bytes.length);
+      return new String(bytes, charset);
+    }
+    return null;
+  }
+
+  @Override
+  public String readString() {
+    return readString(Charset.defaultCharset());
+  }
+
+  @Override
+  public String readString(int offset) {
+    return readString(offset, Charset.defaultCharset());
+  }
+
+  @Override
+  public String readUTF8() {
+    return readString(StandardCharsets.UTF_8);
+  }
+
+  @Override
+  public String readUTF8(int offset) {
+    return readString(offset, StandardCharsets.UTF_8);
+  }
+
+  @Override
+  public Buffer write(Buffer buffer) {
+    int length = Math.min(buffer.remaining(), remaining());
+    write(buffer.bytes(), buffer.offset() + buffer.position(), length);
+    buffer.position(buffer.position() + length);
+    return this;
+  }
+
+  @Override
+  public Buffer write(Bytes bytes) {
+    this.bytes.write(checkWrite(bytes.size()), bytes, 0, bytes.size());
+    return this;
+  }
+
+  @Override
+  public Buffer write(Bytes bytes, int offset, int length) {
+    this.bytes.write(checkWrite(length), bytes, offset, length);
+    return this;
+  }
+
+  @Override
+  public Buffer write(int offset, Bytes bytes, int srcOffset, int length) {
+    this.bytes.write(checkWrite(offset, length), bytes, srcOffset, length);
+    return this;
+  }
+
+  @Override
+  public Buffer write(byte[] bytes) {
+    this.bytes.write(checkWrite(bytes.length), bytes, 0, bytes.length);
+    return this;
+  }
+
+  @Override
+  public Buffer write(byte[] bytes, int offset, int length) {
+    this.bytes.write(checkWrite(length), bytes, offset, length);
+    return this;
+  }
+
+  @Override
+  public Buffer write(int offset, byte[] bytes, int srcOffset, int length) {
+    this.bytes.write(checkWrite(offset, length), bytes, srcOffset, length);
+    return this;
+  }
+
+  @Override
+  public Buffer writeByte(int b) {
+    bytes.writeByte(checkWrite(BYTE), b);
+    return this;
+  }
+
+  @Override
+  public Buffer writeByte(int offset, int b) {
+    bytes.writeByte(checkWrite(offset, BYTE), b);
+    return this;
+  }
+
+  @Override
+  public Buffer writeUnsignedByte(int b) {
+    bytes.writeUnsignedByte(checkWrite(BYTE), b);
+    return this;
+  }
+
+  @Override
+  public Buffer writeUnsignedByte(int offset, int b) {
+    bytes.writeUnsignedByte(checkWrite(offset, BYTE), b);
+    return this;
+  }
+
+  @Override
+  public Buffer writeChar(char c) {
+    bytes.writeChar(checkWrite(Bytes.CHARACTER), c);
+    return this;
+  }
+
+  @Override
+  public Buffer writeChar(int offset, char c) {
+    bytes.writeChar(checkWrite(offset, Bytes.CHARACTER), c);
+    return this;
+  }
+
+  @Override
+  public Buffer writeShort(short s) {
+    bytes.writeShort(checkWrite(SHORT), s);
+    return this;
+  }
+
+  @Override
+  public Buffer writeShort(int offset, short s) {
+    bytes.writeShort(checkWrite(offset, SHORT), s);
+    return this;
+  }
+
+  @Override
+  public Buffer writeUnsignedShort(int s) {
+    bytes.writeUnsignedShort(checkWrite(SHORT), s);
+    return this;
+  }
+
+  @Override
+  public Buffer writeUnsignedShort(int offset, int s) {
+    bytes.writeUnsignedShort(checkWrite(offset, SHORT), s);
+    return this;
+  }
+
+  @Override
+  public Buffer writeMedium(int m) {
+    bytes.writeMedium(checkWrite(3), m);
+    return this;
+  }
+
+  @Override
+  public Buffer writeMedium(int offset, int m) {
+    bytes.writeMedium(checkWrite(offset, 3), m);
+    return this;
+  }
+
+  @Override
+  public Buffer writeUnsignedMedium(int m) {
+    bytes.writeUnsignedMedium(checkWrite(3), m);
+    return this;
+  }
+
+  @Override
+  public Buffer writeUnsignedMedium(int offset, int m) {
+    bytes.writeUnsignedMedium(checkWrite(offset, 3), m);
+    return this;
+  }
+
+  @Override
+  public Buffer writeInt(int i) {
+    bytes.writeInt(checkWrite(Bytes.INTEGER), i);
+    return this;
+  }
+
+  @Override
+  public Buffer writeInt(int offset, int i) {
+    bytes.writeInt(checkWrite(offset, Bytes.INTEGER), i);
+    return this;
+  }
+
+  @Override
+  public Buffer writeUnsignedInt(long i) {
+    bytes.writeUnsignedInt(checkWrite(Bytes.INTEGER), i);
+    return this;
+  }
+
+  @Override
+  public Buffer writeUnsignedInt(int offset, long i) {
+    bytes.writeUnsignedInt(checkWrite(offset, Bytes.INTEGER), i);
+    return this;
+  }
+
+  @Override
+  public Buffer writeLong(long l) {
+    bytes.writeLong(checkWrite(Bytes.LONG), l);
+    return this;
+  }
+
+  @Override
+  public Buffer writeLong(int offset, long l) {
+    bytes.writeLong(checkWrite(offset, Bytes.LONG), l);
+    return this;
+  }
+
+  @Override
+  public Buffer writeFloat(float f) {
+    bytes.writeFloat(checkWrite(Bytes.FLOAT), f);
+    return this;
+  }
+
+  @Override
+  public Buffer writeFloat(int offset, float f) {
+    bytes.writeFloat(checkWrite(offset, Bytes.FLOAT), f);
+    return this;
+  }
+
+  @Override
+  public Buffer writeDouble(double d) {
+    bytes.writeDouble(checkWrite(Bytes.DOUBLE), d);
+    return this;
+  }
+
+  @Override
+  public Buffer writeDouble(int offset, double d) {
+    bytes.writeDouble(checkWrite(offset, Bytes.DOUBLE), d);
+    return this;
+  }
+
+  @Override
+  public Buffer writeBoolean(boolean b) {
+    bytes.writeBoolean(checkWrite(BYTE), b);
+    return this;
+  }
+
+  @Override
+  public Buffer writeBoolean(int offset, boolean b) {
+    bytes.writeBoolean(checkWrite(offset, BYTE), b);
+    return this;
+  }
+
+  @Override
+  public Buffer writeString(String s, Charset charset) {
+    if (s == null) {
+      return writeBoolean(checkWrite(BOOLEAN), Boolean.FALSE);
+    } else {
+      byte[] bytes = s.getBytes(charset);
+      checkWrite(position, BOOLEAN + SHORT + bytes.length);
+      writeBoolean(Boolean.TRUE)
+          .writeUnsignedShort(bytes.length)
+          .write(bytes, 0, bytes.length);
+      return this;
+    }
+  }
+
+  @Override
+  public Buffer writeString(int offset, String s, Charset charset) {
+    if (s == null) {
+      return writeBoolean(checkWrite(offset, BOOLEAN), Boolean.FALSE);
+    } else {
+      byte[] bytes = s.getBytes(charset);
+      checkWrite(offset, BOOLEAN + SHORT + bytes.length);
+      writeBoolean(offset, Boolean.TRUE)
+          .writeUnsignedShort(offset + BOOLEAN, bytes.length)
+          .write(offset + BOOLEAN + SHORT, bytes, 0, bytes.length);
+      return this;
+    }
+  }
+
+  @Override
+  public Buffer writeString(String s) {
+    return writeString(s, Charset.defaultCharset());
+  }
+
+  @Override
+  public Buffer writeString(int offset, String s) {
+    return writeString(offset, s, Charset.defaultCharset());
+  }
+
+  @Override
+  public Buffer writeUTF8(String s) {
+    return writeString(s, StandardCharsets.UTF_8);
+  }
+
+  @Override
+  public Buffer writeUTF8(int offset, String s) {
+    return writeString(offset, s, StandardCharsets.UTF_8);
+  }
+
+  @Override
+  public Buffer flush() {
+    bytes.flush();
+    return this;
+  }
+
+  @Override
+  public void close() {
+    references.set(0);
+    if (referenceManager != null) {
+      referenceManager.release(this);
+    } else {
+      bytes.close();
+    }
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/AbstractBytes.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/AbstractBytes.java
new file mode 100644 (file)
index 0000000..c7edbb6
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Abstract bytes implementation.
+ * <p>
+ * This class provides common state and bounds checking functionality for all {@link Bytes} implementations.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public abstract class AbstractBytes implements Bytes {
+  static final int MAX_SIZE = Integer.MAX_VALUE - 5;
+
+  private boolean open = true;
+  private SwappedBytes swap;
+
+  /**
+   * Checks whether the block is open.
+   */
+  protected void checkOpen() {
+    if (!open) {
+      throw new IllegalStateException("bytes not open");
+    }
+  }
+
+  /**
+   * Checks that the offset is within the bounds of the buffer.
+   */
+  protected void checkOffset(int offset) {
+    checkOpen();
+    if (offset < 0 || offset > size()) {
+      throw new IndexOutOfBoundsException();
+    }
+  }
+
+  /**
+   * Checks bounds for a read.
+   */
+  protected int checkRead(int offset, int length) {
+    checkOffset(offset);
+    int position = offset + length;
+    if (position > size()) {
+      throw new BufferUnderflowException();
+    }
+    return position;
+  }
+
+  /**
+   * Checks bounds for a write.
+   */
+  protected int checkWrite(int offset, int length) {
+    checkOffset(offset);
+    int position = offset + length;
+    if (position > size()) {
+      throw new BufferOverflowException();
+    }
+    return position;
+  }
+
+  @Override
+  public boolean isDirect() {
+    return false;
+  }
+
+  @Override
+  public boolean isFile() {
+    return false;
+  }
+
+  @Override
+  public ByteOrder order() {
+    return ByteOrder.BIG_ENDIAN;
+  }
+
+  @Override
+  public Bytes order(ByteOrder order) {
+    if (order == null) {
+      throw new NullPointerException("order cannot be null");
+    }
+    if (order == order()) {
+      return this;
+    }
+    if (swap != null) {
+      return swap;
+    }
+    swap = new SwappedBytes(this);
+    return swap;
+  }
+
+  @Override
+  public boolean readBoolean(int offset) {
+    return readByte(offset) == 1;
+  }
+
+  @Override
+  public int readUnsignedByte(int offset) {
+    return readByte(offset) & 0xFF;
+  }
+
+  @Override
+  public int readUnsignedShort(int offset) {
+    return readShort(offset) & 0xFFFF;
+  }
+
+  @Override
+  public int readMedium(int offset) {
+    return (readByte(offset)) << 16
+        | (readByte(offset + 1) & 0xff) << 8
+        | (readByte(offset + 2) & 0xff);
+  }
+
+  @Override
+  public int readUnsignedMedium(int offset) {
+    return (readByte(offset) & 0xff) << 16
+        | (readByte(offset + 1) & 0xff) << 8
+        | (readByte(offset + 2) & 0xff);
+  }
+
+  @Override
+  public long readUnsignedInt(int offset) {
+    return readInt(offset) & 0xFFFFFFFFL;
+  }
+
+  @Override
+  public String readString(int offset) {
+    return readString(offset, Charset.defaultCharset());
+  }
+
+  @Override
+  public String readString(int offset, Charset charset) {
+    if (readBoolean(offset)) {
+      byte[] bytes = new byte[readUnsignedShort(offset + BYTE)];
+      read(offset + BYTE + SHORT, bytes, 0, bytes.length);
+      return new String(bytes, charset);
+    }
+    return null;
+  }
+
+  @Override
+  public String readUTF8(int offset) {
+    return readString(offset, StandardCharsets.UTF_8);
+  }
+
+  @Override
+  public Bytes writeBoolean(int offset, boolean b) {
+    return writeByte(offset, b ? 1 : 0);
+  }
+
+  @Override
+  public Bytes writeUnsignedByte(int offset, int b) {
+    return writeByte(offset, (byte) b);
+  }
+
+  @Override
+  public Bytes writeUnsignedShort(int offset, int s) {
+    return writeShort(offset, (short) s);
+  }
+
+  @Override
+  public Bytes writeMedium(int offset, int m) {
+    writeByte(offset, (byte) (m >>> 16));
+    writeByte(offset + 1, (byte) (m >>> 8));
+    writeByte(offset + 2, (byte) m);
+    return this;
+  }
+
+  @Override
+  public Bytes writeUnsignedMedium(int offset, int m) {
+    return writeMedium(offset, m);
+  }
+
+  @Override
+  public Bytes writeUnsignedInt(int offset, long i) {
+    return writeInt(offset, (int) i);
+  }
+
+  @Override
+  public Bytes writeString(int offset, String s) {
+    return writeString(offset, s, Charset.defaultCharset());
+  }
+
+  @Override
+  public Bytes writeString(int offset, String s, Charset charset) {
+    if (s == null) {
+      return writeBoolean(offset, Boolean.FALSE);
+    } else {
+      writeBoolean(offset, Boolean.TRUE);
+      byte[] bytes = s.getBytes(charset);
+      return writeUnsignedShort(offset + BYTE, bytes.length)
+          .write(offset + BYTE + SHORT, bytes, 0, bytes.length);
+    }
+  }
+
+  @Override
+  public Bytes writeUTF8(int offset, String s) {
+    return writeString(offset, s, StandardCharsets.UTF_8);
+  }
+
+  @Override
+  public Bytes flush() {
+    return this;
+  }
+
+  @Override
+  public void close() {
+    open = false;
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/Buffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/Buffer.java
new file mode 100644 (file)
index 0000000..fe29597
--- /dev/null
@@ -0,0 +1,1387 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.concurrent.ReferenceCounted;
+
+import java.nio.ByteOrder;
+
+/**
+ * Navigable byte buffer for input/output operations.
+ * <p>
+ * The byte buffer provides a fluent interface for reading bytes from and writing bytes to some underlying storage
+ * implementation. The {@code Buffer} type is agnostic about the specific underlying storage implementation, but different
+ * buffer implementations may be designed for specific storage layers.
+ * <p>
+ * Aside from the underlying storage implementation, this buffer works very similarly to Java's {@link java.nio.ByteBuffer}.
+ * It intentionally exposes methods that can be easily understood by any developer with experience with {@code ByteBuffer}.
+ * <p>
+ * In order to support reading and writing from buffers, {@code Buffer} implementations maintain a series of pointers to
+ * aid in navigating the buffer.
+ * <p>
+ * Most notable of these pointers is the {@code position}. When values are written to or read from the buffer, the
+ * buffer increments its internal {@code position} according to the number of bytes read. This allows users to iterate
+ * through the bytes in the buffer without maintaining external pointers.
+ * <p>
+ * <pre>
+ *   {@code
+ *      try (Buffer buffer = DirectBuffer.allocate(1024)) {
+ *        buffer.writeInt(1);
+ *        buffer.flip();
+ *        assert buffer.readInt() == 1;
+ *      }
+ *   }
+ * </pre>
+ * <p>
+ * Buffers implement {@link ReferenceCounted} in order to keep track of the number of currently
+ * held references to a given buffer. When a buffer is constructed, the buffer contains only {@code 1} reference. This
+ * reference can be released via the {@link Buffer#close()} method which can be called automatically
+ * by using a try-with-resources statement demonstrated above. Additional references to the buffer should be acquired via
+ * {@link ReferenceCounted#acquire()} and released via
+ * {@link ReferenceCounted#release}. Once all references to a buffer have been released - including
+ * the initial reference - the memory will be freed (for in-memory buffers) and writes will be automatically flushed to
+ * disk (for persistent buffers).
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface Buffer extends BytesInput<Buffer>, BufferInput<Buffer>, BytesOutput<Buffer>, BufferOutput<Buffer>, ReferenceCounted<Buffer> {
+
+  /**
+   * Returns whether the buffer has an array.
+   *
+   * @return Whether the buffer has an underlying array.
+   */
+  default boolean hasArray() {
+    return false;
+  }
+
+  /**
+   * Returns the underlying byte array.
+   *
+   * @return the underlying byte array
+   * @throws UnsupportedOperationException if a heap array is not supported
+   */
+  default byte[] array() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Returns the byte order.
+   * <p>
+   * For consistency with {@link java.nio.ByteBuffer}, all buffer implementations are initially in {@link ByteOrder#BIG_ENDIAN} order.
+   *
+   * @return The byte order.
+   */
+  ByteOrder order();
+
+  /**
+   * Sets the byte order, returning a new swapped {@link Buffer} instance.
+   * <p>
+   * By default, all buffers are read and written in {@link ByteOrder#BIG_ENDIAN} order. This provides complete
+   * consistency with {@link java.nio.ByteBuffer}. To flip buffers to {@link ByteOrder#LITTLE_ENDIAN} order, this
+   * buffer's {@code Bytes} instance is decorated by a {@link SwappedBytes} instance which will reverse
+   * read and written bytes using, e.g. {@link Integer#reverseBytes(int)}.
+   *
+   * @param order The byte order.
+   * @return The updated buffer.
+   */
+  Buffer order(ByteOrder order);
+
+  /**
+   * Returns a boolean value indicating whether the buffer is a direct buffer.
+   *
+   * @return Indicates whether the buffer is a direct buffer.
+   */
+  boolean isDirect();
+
+  /**
+   * Returns a boolean value indicating whether the buffer is a read-only buffer.
+   *
+   * @return Indicates whether the buffer is a read-only buffer.
+   */
+  boolean isReadOnly();
+
+  /**
+   * Returns a boolean value indicating whether the buffer is backed by a file.
+   *
+   * @return Indicates whether the buffer is backed by a file.
+   */
+  boolean isFile();
+
+  /**
+   * Returns a read-only view of the buffer.
+   * <p>
+   * The returned buffer will share the underlying {@link Bytes} with which buffer, but the buffer's {@code limit},
+   * {@code capacity}, and {@code position} will be independent of this buffer.
+   *
+   * @return A read-only buffer.
+   */
+  Buffer asReadOnlyBuffer();
+
+  /**
+   * Returns the buffer's starting offset within the underlying {@link Bytes}.
+   * <p>
+   * The offset is used to calculate the absolute position of the buffer's relative {@link Buffer#position() position}
+   * within the underlying {@link Bytes}.
+   *
+   * @return The buffer's offset.
+   */
+  int offset();
+
+  /**
+   * Returns the buffer's capacity.
+   * <p>
+   * The capacity represents the total amount of storage space allocated to the buffer by the underlying storage
+   * implementation. As bytes are written to the buffer, the buffer's capacity may grow up to {@link Buffer#maxCapacity()}.
+   *
+   * @return The buffer's capacity.
+   */
+  int capacity();
+
+  /**
+   * Sets the buffer's capacity.
+   * <p>
+   * The given capacity must be greater than the current {@link Buffer#capacity() capacity} and less than or equal to
+   * {@link Buffer#maxCapacity()}. When the capacity is changed, the underlying {@link Bytes} will be resized via
+   * {@link Bytes#resize(int)}.
+   *
+   * @param capacity The capacity to which to resize the buffer.
+   * @return The resized buffer.
+   * @throws IllegalArgumentException If the given {@code capacity} is less than the current {@code capacity}
+   *                                  or greater than {@link Buffer#maxCapacity()}
+   */
+  Buffer capacity(int capacity);
+
+  /**
+   * Returns the maximum allowed capacity for the buffer.
+   * <p>
+   * The maximum capacity is the limit up to which this buffer's {@link Buffer#capacity() capacity} can be expanded.
+   * While the capacity grows, the maximum capacity is fixed from the moment the buffer is created and cannot change.
+   *
+   * @return The buffer's maximum capacity.
+   */
+  int maxCapacity();
+
+  /**
+   * Sets the buffer's current read/write position.
+   * <p>
+   * The position is an internal cursor that tracks where to write/read bytes in the underlying storage implementation.
+   *
+   * @param position The position to set.
+   * @return This buffer.
+   * @throws IllegalArgumentException If the given position is less than {@code 0} or more than {@link Buffer#limit()}
+   */
+  Buffer position(int position);
+
+  /**
+   * Returns the buffer's read/write limit.
+   * <p>
+   * The limit dictates the highest position to which bytes can be read from or written to the buffer. If the limit is
+   * not explicitly set then it will always equal the buffer's {@link Buffer#maxCapacity() maxCapacity}. Note that the
+   * limit may be set by related methods such as {@link Buffer#flip()}
+   *
+   * @return The buffer's limit.
+   */
+  int limit();
+
+  /**
+   * Sets the buffer's read/write limit.
+   * <p>
+   * The limit dictates the highest position to which bytes can be read from or written to the buffer. The limit must
+   * be within the bounds of the buffer, i.e. greater than {@code 0} and less than or equal to {@link Buffer#capacity()}
+   *
+   * @param limit The limit to set.
+   * @return This buffer.
+   * @throws IllegalArgumentException If the given limit is less than {@code 0} or more than {@link Buffer#capacity()}
+   */
+  Buffer limit(int limit);
+
+  /**
+   * Returns the number of bytes remaining in the buffer until the {@link Buffer#limit()} is reached.
+   * <p>
+   * The bytes remaining is calculated by {@code buffer.limit() - buffer.position()}. If no limit is set on the buffer
+   * then the {@link Buffer#maxCapacity() maxCapacity} will be used.
+   *
+   * @return The number of bytes remaining in the buffer.
+   */
+  @Override
+  int remaining();
+
+  /**
+   * Returns a boolean indicating whether the buffer has bytes remaining.
+   * <p>
+   * If {@link Buffer#remaining()} is greater than {@code 0} then this method will return {@code true}, otherwise
+   * {@code false}
+   *
+   * @return Indicates whether bytes are remaining in the buffer. {@code true} if {@link Buffer#remaining()} is
+   * greater than {@code 0}, {@code false} otherwise.
+   */
+  @Override
+  boolean hasRemaining();
+
+  /**
+   * Flips the buffer.
+   * <p>
+   * The limit is set to the current position and then the position is set to zero. If the mark is defined then it is discarded.
+   * <pre>
+   *   {@code
+   *   assert buffer.writeLong(1234).flip().readLong() == 1234;
+   *   }
+   * </pre>
+   *
+   * @return This buffer.
+   */
+  Buffer flip();
+
+  /**
+   * Sets a mark at the current position.
+   * <p>
+   * The mark is a simple internal reference to the buffer's current position. Marks can be used to reset the buffer
+   * to a specific position after some operation.
+   * <p>
+   * <pre>
+   *   {@code
+   *   buffer.mark();
+   *   buffer.writeInt(1).writeBoolean(true);
+   *   buffer.reset();
+   *   assert buffer.readInt() == 1;
+   *   }
+   * </pre>
+   *
+   * @return This buffer.
+   */
+  Buffer mark();
+
+  /**
+   * Resets the buffer's position to the previously-marked position.
+   * <p>
+   * Invoking this method neither changes nor discards the mark's value.
+   *
+   * @return This buffer.
+   * @throws java.nio.InvalidMarkException If no mark is set.
+   */
+  Buffer reset();
+
+  /**
+   * Rewinds the buffer. The position is set to zero and the mark is discarded.
+   *
+   * @return This buffer.
+   */
+  Buffer rewind();
+
+  /**
+   * Advances the buffer's {@code position} {@code length} bytes.
+   *
+   * @param length The number of bytes to advance this buffer's {@code position}.
+   * @return This buffer.
+   * @throws IndexOutOfBoundsException If {@code length} is greater than {@link Buffer#remaining()}
+   */
+  @Override
+  Buffer skip(int length);
+
+  /**
+   * Clears the buffer.
+   * <p>
+   * The position is set to zero, the limit is set to the capacity, and the mark is discarded.
+   *
+   * @return This buffer.
+   */
+  Buffer clear();
+
+  /**
+   * Compacts the buffer, moving bytes from the current position to the end of the buffer to the head of the buffer.
+   *
+   * @return This buffer.
+   */
+  Buffer compact();
+
+  /**
+   * Returns a duplicate of the buffer.
+   *
+   * @return A duplicate buffer.
+   */
+  Buffer duplicate();
+
+  /**
+   * Returns the bytes underlying the buffer.
+   * <p>
+   * The buffer is a wrapper around {@link Bytes} that handles writing sequences of bytes by tracking positions and
+   * limits. This method returns the {@link Bytes} that this buffer wraps.
+   *
+   * @return The underlying bytes.
+   */
+  Bytes bytes();
+
+  /**
+   * Returns a view of this buffer starting at the current position.
+   * <p>
+   * The returned buffer will contain the same underlying {@link Bytes} instance as this buffer, but its pointers will
+   * be offset by the current {@code position} of this buffer at the time the slice is created. Calls to
+   * {@link Buffer#rewind()} and similar methods on the resulting {@link Buffer} will result in
+   * the buffer's {@code position} being reset to the {@code position} of this buffer at the time the slice was created.
+   * <p>
+   * The returned buffer is reference counted separately from this buffer. Therefore, closing the returned buffer will
+   * release the buffer back to the internal buffer poll and will not result in this buffer being closed. Users should
+   * always call {@link Buffer#close()} once finished using the buffer slice.
+   *
+   * @return A slice of this buffer.
+   * @see Buffer#slice(int)
+   * @see Buffer#slice(int, int)
+   */
+  Buffer slice();
+
+  /**
+   * Returns a view of this buffer of the given length starting at the current position.
+   * <p>
+   * The returned buffer will contain the same underlying {@link Bytes} instance as this buffer, but its pointers will
+   * be offset by the current {@code position} of this buffer at the time the slice is created. Calls to
+   * {@link Buffer#rewind()} and similar methods on the resulting {@link Buffer} will result in
+   * the buffer's {@code position} being reset to the {@code position} of this buffer at the time the slice was created.
+   * <p>
+   * The returned buffer is reference counted separately from this buffer. Therefore, closing the returned buffer will
+   * release the buffer back to the internal buffer poll and will not result in this buffer being closed. Users should
+   * always call {@link Buffer#close()} once finished using the buffer slice.
+   *
+   * @param length The length of the slice.
+   * @return A slice of this buffer.
+   * @see Buffer#slice()
+   * @see Buffer#slice(int, int)
+   */
+  Buffer slice(int length);
+
+  /**
+   * Returns a view of this buffer starting at the given offset with the given length.
+   * <p>
+   * The returned buffer will contain the same underlying {@link Bytes} instance as this buffer, but its pointers will
+   * be offset by the given {@code offset} and its length limited by the given {@code length}. Calls to
+   * {@link Buffer#rewind()} and similar methods on the resulting {@link Buffer} will result in
+   * the buffer's {@code position} being reset to the given {@code offset}.
+   * <p>
+   * The returned buffer is reference counted separately from this buffer. Therefore, closing the returned buffer will
+   * release the buffer back to the internal buffer poll and will not result in this buffer being closed. Users should
+   * always call {@link Buffer#close()} once finished using the buffer slice.
+   *
+   * @param offset The offset at which to begin the slice.
+   * @param length The number of bytes in the slice.
+   * @return The buffer slice.
+   * @throws IndexOutOfBoundsException         If the given offset is not contained within the bounds of this buffer
+   * @throws java.nio.BufferUnderflowException If the length of the remaining bytes in the buffer is less than {@code length}
+   * @see Buffer#slice()
+   * @see Buffer#slice(int)
+   */
+  Buffer slice(int offset, int length);
+
+  /**
+   * Reads bytes into the given buffer.
+   * <p>
+   * Bytes will be read starting at the current buffer position until either {@link Buffer#limit()} has been reached.
+   * If {@link Buffer#remaining()} is less than the {@link Buffer#remaining()} of the given buffer, a
+   * {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @param buffer The buffer into which to read bytes.
+   * @return The buffer.
+   * @throws java.nio.BufferUnderflowException If the given {@link Buffer#remaining()} is greater than this buffer's
+   *                                           {@link Buffer#remaining()}
+   */
+  @Override
+  Buffer read(Buffer buffer);
+
+  /**
+   * Reads bytes into the given byte array.
+   * <p>
+   * Bytes will be read starting at the current buffer position until either the byte array {@code length} or the
+   * {@link Buffer#limit()} has been reached. If {@link Buffer#remaining()}
+   * is less than the {@code length} of the given byte array, a {@link java.nio.BufferUnderflowException} will be
+   * thrown.
+   *
+   * @param bytes The byte array into which to read bytes.
+   * @return The buffer.
+   * @throws java.nio.BufferUnderflowException If the given byte array's {@code length} is greater than
+   *                                           {@link Buffer#remaining()}
+   * @see Buffer#read(Bytes, int, int)
+   * @see Buffer#read(int, Bytes, int, int)
+   */
+  @Override
+  Buffer read(Bytes bytes);
+
+  /**
+   * Reads bytes into the given byte array.
+   * <p>
+   * Bytes will be read starting at the current buffer position until either the byte array {@code length} or the
+   * {@link Buffer#limit()} has been reached. If {@link Buffer#remaining()}
+   * is less than the {@code length} of the given byte array, a {@link java.nio.BufferUnderflowException} will be
+   * thrown.
+   *
+   * @param bytes The byte array into which to read bytes.
+   * @return The buffer.
+   * @throws java.nio.BufferUnderflowException If the given byte array's {@code length} is greater than
+   *                                           {@link Buffer#remaining()}
+   * @see Buffer#read(byte[], int, int)
+   * @see Buffer#read(int, byte[], int, int)
+   */
+  @Override
+  Buffer read(byte[] bytes);
+
+  /**
+   * Reads bytes into the given byte array starting at the current position.
+   * <p>
+   * Bytes will be read from the current position up to the given length. If the provided {@code length} is
+   * greater than {@link Buffer#remaining()} then a {@link java.nio.BufferUnderflowException} will
+   * be thrown. If the {@code offset} is out of bounds of the buffer then an {@link IndexOutOfBoundsException}
+   * will be thrown.
+   *
+   * @param bytes     The byte array into which to read bytes.
+   * @param dstOffset The offset at which to write bytes into the given buffer
+   * @return The buffer.
+   * @throws java.nio.BufferUnderflowException If {@code length} is greater than {@link Buffer#remaining()}
+   * @throws IndexOutOfBoundsException         If the given offset is out of the bounds of the buffer. Note that
+   *                                           bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#read(Bytes)
+   * @see Buffer#read(int, Bytes, int, int)
+   */
+  @Override
+  Buffer read(Bytes bytes, int dstOffset, int length);
+
+  /**
+   * Reads bytes into the given byte array starting at the given offset up to the given length.
+   * <p>
+   * Bytes will be read from the given starting offset up to the given length. If the provided {@code length} is
+   * greater than {@link Buffer#limit() - srcOffset} then a {@link java.nio.BufferUnderflowException} will
+   * be thrown. If the {@code srcOffset} is out of bounds of the buffer then an {@link IndexOutOfBoundsException}
+   * will be thrown.
+   *
+   * @param srcOffset The offset from which to start reading bytes.
+   * @param bytes     The byte array into which to read bytes.
+   * @param dstOffset The offset at which to write bytes into the given buffer
+   * @return The buffer.
+   * @throws java.nio.BufferUnderflowException If {@code length} is greater than {@link Buffer#remaining()}
+   * @throws IndexOutOfBoundsException         If the given offset is out of the bounds of the buffer. Note that
+   *                                           bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#read(Bytes)
+   * @see Buffer#read(Bytes, int, int)
+   */
+  @Override
+  Buffer read(int srcOffset, Bytes bytes, int dstOffset, int length);
+
+  /**
+   * Reads bytes into the given byte array starting at current position up to the given length.
+   * <p>
+   * Bytes will be read from the current position up to the given length. If the provided {@code length} is
+   * greater than {@link Buffer#remaining()} then a {@link java.nio.BufferUnderflowException} will
+   * be thrown. If the {@code offset} is out of bounds of the buffer then an {@link IndexOutOfBoundsException}
+   * will be thrown.
+   *
+   * @param bytes  The byte array into which to read bytes.
+   * @param offset The offset at which to write bytes into the given buffer
+   * @return The buffer.
+   * @throws java.nio.BufferUnderflowException If {@code length} is greater than {@link Buffer#remaining()}
+   * @throws IndexOutOfBoundsException         If the given offset is out of the bounds of the buffer. Note that
+   *                                           bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#read(byte[])
+   * @see Buffer#read(int, byte[], int, int)
+   */
+  @Override
+  Buffer read(byte[] bytes, int offset, int length);
+
+  /**
+   * Reads bytes into the given byte array starting at the given offset up to the given length.
+   * <p>
+   * Bytes will be read from the given starting offset up to the given length. If the provided {@code length} is
+   * greater than {@link Buffer#remaining()} then a {@link java.nio.BufferUnderflowException} will
+   * be thrown. If the {@code offset} is out of bounds of the buffer then an {@link IndexOutOfBoundsException}
+   * will be thrown.
+   *
+   * @param srcOffset The offset from which to start reading bytes.
+   * @param bytes     The byte array into which to read bytes.
+   * @param dstOffset The offset at which to write bytes into the given buffer
+   * @return The buffer.
+   * @throws java.nio.BufferUnderflowException If {@code length} is greater than {@link Buffer#remaining()}
+   * @throws IndexOutOfBoundsException         If the given offset is out of the bounds of the buffer. Note that
+   *                                           bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#read(byte[])
+   * @see Buffer#read(byte[], int, int)
+   */
+  @Override
+  Buffer read(int srcOffset, byte[] bytes, int dstOffset, int length);
+
+  /**
+   * Reads a byte from the buffer at the current position.
+   * <p>
+   * When the byte is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#BYTE}. If
+   * there are no bytes remaining in the buffer then a {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read byte.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#BYTE}
+   * @see Buffer#readByte(int)
+   */
+  @Override
+  int readByte();
+
+  /**
+   * Reads a byte from the buffer at the given offset.
+   * <p>
+   * The byte will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the byte.
+   * @return The read byte.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readByte()
+   */
+  @Override
+  int readByte(int offset);
+
+  /**
+   * Reads an unsigned byte from the buffer at the current position.
+   * <p>
+   * When the byte is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#BYTE}. If
+   * there are no bytes remaining in the buffer then a {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read byte.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#BYTE}
+   * @see Buffer#readUnsignedByte(int)
+   */
+  @Override
+  int readUnsignedByte();
+
+  /**
+   * Reads an unsigned byte from the buffer at the given offset.
+   * <p>
+   * The byte will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the byte.
+   * @return The read byte.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readUnsignedByte()
+   */
+  @Override
+  int readUnsignedByte(int offset);
+
+  /**
+   * Reads a 16-bit character from the buffer at the current position.
+   * <p>
+   * When the character is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#CHARACTER}.
+   * If there are less than {@link Bytes#CHARACTER} bytes remaining in the buffer then a
+   * {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read character.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#CHARACTER}
+   * @see Buffer#readChar(int)
+   */
+  @Override
+  char readChar();
+
+  /**
+   * Reads a 16-bit character from the buffer at the given offset.
+   * <p>
+   * The character will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the character.
+   * @return The read character.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readChar()
+   */
+  @Override
+  char readChar(int offset);
+
+  /**
+   * Reads a 16-bit signed integer from the buffer at the current position.
+   * <p>
+   * When the short is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#SHORT}.
+   * If there are less than {@link Bytes#SHORT} bytes remaining in the buffer then a
+   * {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read short.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#SHORT}
+   * @see Buffer#readShort(int)
+   */
+  @Override
+  short readShort();
+
+  /**
+   * Reads a 16-bit signed integer from the buffer at the given offset.
+   * <p>
+   * The short will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the short.
+   * @return The read short.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readShort()
+   */
+  @Override
+  short readShort(int offset);
+
+  /**
+   * Reads a 16-bit unsigned integer from the buffer at the current position.
+   * <p>
+   * When the short is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#SHORT}.
+   * If there are less than {@link Bytes#SHORT} bytes remaining in the buffer then a
+   * {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read short.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#SHORT}
+   * @see Buffer#readUnsignedShort(int)
+   */
+  @Override
+  int readUnsignedShort();
+
+  /**
+   * Reads a 16-bit unsigned integer from the buffer at the given offset.
+   * <p>
+   * The short will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the short.
+   * @return The read short.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readUnsignedShort()
+   */
+  @Override
+  int readUnsignedShort(int offset);
+
+  /**
+   * Reads a 32-bit signed integer from the buffer at the current position.
+   * <p>
+   * When the integer is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#INTEGER}.
+   * If there are less than {@link Bytes#INTEGER} bytes remaining in the buffer then a
+   * {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read integer.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#INTEGER}
+   * @see Buffer#readInt(int)
+   */
+  @Override
+  int readInt();
+
+  /**
+   * Reads a 32-bit signed integer from the buffer at the given offset.
+   * <p>
+   * The integer will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the integer.
+   * @return The read integer.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readInt()
+   */
+  @Override
+  int readInt(int offset);
+
+  /**
+   * Reads a 32-bit unsigned integer from the buffer at the current position.
+   * <p>
+   * When the integer is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#INTEGER}.
+   * If there are less than {@link Bytes#INTEGER} bytes remaining in the buffer then a
+   * {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read integer.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#INTEGER}
+   * @see Buffer#readUnsignedInt(int)
+   */
+  @Override
+  long readUnsignedInt();
+
+  /**
+   * Reads a 32-bit unsigned integer from the buffer at the given offset.
+   * <p>
+   * The integer will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the integer.
+   * @return The read integer.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readUnsignedInt()
+   */
+  @Override
+  long readUnsignedInt(int offset);
+
+  /**
+   * Reads a 64-bit signed integer from the buffer at the current position.
+   * <p>
+   * When the long is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#LONG}.
+   * If there are less than {@link Bytes#LONG} bytes remaining in the buffer then a
+   * {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read long.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#LONG}
+   * @see Buffer#readLong(int)
+   */
+  @Override
+  long readLong();
+
+  /**
+   * Reads a 64-bit signed integer from the buffer at the given offset.
+   * <p>
+   * The long will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the long.
+   * @return The read long.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readLong()
+   */
+  @Override
+  long readLong(int offset);
+
+  /**
+   * Reads a single-precision 32-bit floating point number from the buffer at the current position.
+   * <p>
+   * When the float is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#FLOAT}.
+   * If there are less than {@link Bytes#FLOAT} bytes remaining in the buffer then a
+   * {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read float.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#FLOAT}
+   * @see Buffer#readFloat(int)
+   */
+  @Override
+  float readFloat();
+
+  /**
+   * Reads a single-precision 32-bit floating point number from the buffer at the given offset.
+   * <p>
+   * The float will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the float.
+   * @return The read float.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readFloat()
+   */
+  @Override
+  float readFloat(int offset);
+
+  /**
+   * Reads a double-precision 64-bit floating point number from the buffer at the current position.
+   * <p>
+   * When the double is read from the buffer, the buffer's {@code position} will be advanced by {@link Bytes#DOUBLE}.
+   * If there are less than {@link Bytes#DOUBLE} bytes remaining in the buffer then a
+   * {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read double.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@link Bytes#DOUBLE}
+   * @see Buffer#readDouble(int)
+   */
+  @Override
+  double readDouble();
+
+  /**
+   * Reads a double-precision 64-bit floating point number from the buffer at the given offset.
+   * <p>
+   * The double will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the double.
+   * @return The read double.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readDouble()
+   */
+  @Override
+  double readDouble(int offset);
+
+  /**
+   * Reads a 1 byte boolean from the buffer at the current position.
+   * <p>
+   * When the boolean is read from the buffer, the buffer's {@code position} will be advanced by {@code 1}.
+   * If there are no bytes remaining in the buffer then a {@link java.nio.BufferUnderflowException} will be thrown.
+   *
+   * @return The read boolean.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@code 1}
+   * @see Buffer#readBoolean(int)
+   */
+  @Override
+  boolean readBoolean();
+
+  /**
+   * Reads a 1 byte boolean from the buffer at the given offset.
+   * <p>
+   * The boolean will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the boolean.
+   * @return The read boolean.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readBoolean()
+   */
+  @Override
+  boolean readBoolean(int offset);
+
+  /**
+   * Reads a UTF-8 string from the buffer at the current position.
+   * <p>
+   * When the string is read from the buffer, the buffer's {@code position} will be advanced by 2 bytes plus the byte
+   * length of the string. If there are no bytes remaining in the buffer then a {@link java.nio.BufferUnderflowException}
+   * will be thrown.
+   *
+   * @return The read string.
+   * @throws java.nio.BufferUnderflowException If {@link Buffer#remaining()} is less than {@code 1}
+   * @see Buffer#readUTF8(int)
+   */
+  @Override
+  String readUTF8();
+
+  /**
+   * Reads a UTF-8 string from the buffer at the given offset.
+   * <p>
+   * The string will be read from the given offset. If the given index is out of the bounds of the buffer then a
+   * {@link IndexOutOfBoundsException} will be thrown.
+   *
+   * @param offset The offset at which to read the boolean.
+   * @return The read string.
+   * @throws IndexOutOfBoundsException If the given offset is out of the bounds of the buffer. Note that
+   *                                   bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#readUTF8()
+   */
+  @Override
+  String readUTF8(int offset);
+
+  /**
+   * Writes a buffer to the buffer.
+   * <p>
+   * When the buffer is written to the buffer, the buffer's {@code position} will be advanced by the number of bytes
+   * in the provided buffer. If the provided {@link Buffer#remaining()} exceeds {@link Buffer#remaining()} then an
+   * {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param buffer The buffer to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If the given buffer's {@link Buffer#remaining()} bytes exceeds this buffer's
+   *                                          remaining bytes.
+   */
+  @Override
+  Buffer write(Buffer buffer);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   * <p>
+   * When the bytes are written to the buffer, the buffer's {@code position} will be advanced by the number of bytes
+   * in the provided byte array. If the number of bytes exceeds {@link Buffer#limit()} then an
+   * {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param bytes The array of bytes to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If the number of bytes exceeds the buffer's remaining bytes.
+   * @see Buffer#write(Bytes, int, int)
+   * @see Buffer#write(int, Bytes, int, int)
+   */
+  @Override
+  Buffer write(Bytes bytes);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   * <p>
+   * When the bytes are written to the buffer, the buffer's {@code position} will be advanced by the number of bytes
+   * in the provided byte array. If the number of bytes exceeds {@link Buffer#limit()} then an
+   * {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param bytes The array of bytes to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If the number of bytes exceeds the buffer's remaining bytes.
+   * @see Buffer#write(byte[], int, int)
+   * @see Buffer#write(int, byte[], int, int)
+   */
+  @Override
+  Buffer write(byte[] bytes);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   * <p>
+   * The bytes will be written starting at the current position up to the given length. If the length of the byte array
+   * is larger than the provided {@code length} then only {@code length} bytes will be read from the array. If the
+   * provided {@code length} is greater than the remaining bytes in this buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param bytes  The array of bytes to write.
+   * @param offset The offset at which to start writing the bytes.
+   * @param length The number of bytes from the provided byte array to write to the buffer.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If there are not enough bytes remaining in the buffer.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer.
+   * @see Buffer#write(Bytes)
+   * @see Buffer#write(int, Bytes, int, int)
+   */
+  @Override
+  Buffer write(Bytes bytes, int offset, int length);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   * <p>
+   * The bytes will be written starting at the given offset up to the given length. If the remaining bytes in the byte array
+   * is larger than the provided {@code length} then only {@code length} bytes will be read from the array. If the
+   * provided {@code length} is greater than {@link Buffer#limit()} minus {@code offset} then a
+   * {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset    The offset at which to start writing the bytes.
+   * @param src       The array of bytes to write.
+   * @param srcOffset The offset at which to begin reading bytes from the source.
+   * @param length    The number of bytes from the provided byte array to write to the buffer.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If there are not enough bytes remaining in the buffer.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer.
+   * @see Buffer#write(Bytes)
+   * @see Buffer#write(Bytes, int, int)
+   */
+  @Override
+  Buffer write(int offset, Bytes src, int srcOffset, int length);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   * <p>
+   * The bytes will be written starting at the current position up to the given length. If the length of the byte array
+   * is larger than the provided {@code length} then only {@code length} bytes will be read from the array. If the
+   * provided {@code length} is greater than the remaining bytes in this buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param bytes  The array of bytes to write.
+   * @param offset The offset at which to start writing the bytes.
+   * @param length The number of bytes from the provided byte array to write to the buffer.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If there are not enough bytes remaining in the buffer.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer.
+   * @see Buffer#write(byte[])
+   * @see Buffer#write(int, byte[], int, int)
+   */
+  @Override
+  Buffer write(byte[] bytes, int offset, int length);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   * <p>
+   * The bytes will be written starting at the given offset up to the given length. If the remaining bytes in the byte array
+   * is larger than the provided {@code length} then only {@code length} bytes will be read from the array. If the
+   * provided {@code length} is greater than {@link Buffer#limit()} minus {@code offset} then a
+   * {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset    The offset at which to start writing the bytes.
+   * @param src       The array of bytes to write.
+   * @param srcOffset The offset at which to begin reading bytes from the source.
+   * @param length    The number of bytes from the provided byte array to write to the buffer.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If there are not enough bytes remaining in the buffer.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer.
+   * @see Buffer#write(byte[])
+   * @see Buffer#write(byte[], int, int)
+   */
+  @Override
+  Buffer write(int offset, byte[] src, int srcOffset, int length);
+
+  /**
+   * Writes a byte to the buffer at the current position.
+   * <p>
+   * When the byte is written to the buffer, the buffer's {@code position} will be advanced by {@link Bytes#BYTE}. If
+   * there are no bytes remaining in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param b The byte to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If there are no bytes remaining in the buffer.
+   * @see Buffer#writeByte(int, int)
+   */
+  @Override
+  Buffer writeByte(int b);
+
+  /**
+   * Writes a byte to the buffer at the given offset.
+   * <p>
+   * The byte will be written at the given offset. If there are no bytes remaining in the buffer then a
+   * {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the byte.
+   * @param b      The byte to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If there are not enough bytes remaining in the buffer.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeByte(int)
+   */
+  @Override
+  Buffer writeByte(int offset, int b);
+
+  /**
+   * Writes an unsigned byte to the buffer at the current position.
+   * <p>
+   * When the byte is written to the buffer, the buffer's {@code position} will be advanced by {@link Bytes#BYTE}. If
+   * there are no bytes remaining in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param b The byte to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If there are no bytes remaining in the buffer.
+   * @see Buffer#writeUnsignedByte(int, int)
+   */
+  @Override
+  Buffer writeUnsignedByte(int b);
+
+  /**
+   * Writes an unsigned byte to the buffer at the given offset.
+   * <p>
+   * The byte will be written at the given offset. If there are no bytes remaining in the buffer then a
+   * {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the byte.
+   * @param b      The byte to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If there are not enough bytes remaining in the buffer.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeUnsignedByte(int)
+   */
+  @Override
+  Buffer writeUnsignedByte(int offset, int b);
+
+  /**
+   * Writes a 16-bit character to the buffer at the current position.
+   * <p>
+   * When the character is written to the buffer, the buffer's {@code position} will be advanced by
+   * {@link Bytes#CHARACTER}. If less than {@code 2} bytes are remaining in the buffer then a
+   * {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param c The character to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#CHARACTER}.
+   * @see Buffer#writeChar(int, char)
+   */
+  @Override
+  Buffer writeChar(char c);
+
+  /**
+   * Writes a 16-bit character to the buffer at the given offset.
+   * <p>
+   * The character will be written at the given offset. If there are less than {@link Bytes#CHARACTER} bytes remaining
+   * in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the character.
+   * @param c      The character to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#CHARACTER}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeChar(char)
+   */
+  @Override
+  Buffer writeChar(int offset, char c);
+
+  /**
+   * Writes a 16-bit signed integer to the buffer at the current position.
+   * <p>
+   * When the short is written to the buffer, the buffer's {@code position} will be advanced by {@link Bytes#SHORT}. If
+   * less than {@link Bytes#SHORT} bytes are remaining in the buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param s The short to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#SHORT}.
+   * @see Buffer#writeShort(int, short)
+   */
+  @Override
+  Buffer writeShort(short s);
+
+  /**
+   * Writes a 16-bit signed integer to the buffer at the given offset.
+   * <p>
+   * The short will be written at the given offset. If there are less than {@link Bytes#SHORT} bytes remaining in the buffer
+   * then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the short.
+   * @param s      The short to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#SHORT}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeShort(short)
+   */
+  @Override
+  Buffer writeShort(int offset, short s);
+
+  /**
+   * Writes a 16-bit signed integer to the buffer at the current position.
+   * <p>
+   * When the short is written to the buffer, the buffer's {@code position} will be advanced by {@link Bytes#SHORT}. If
+   * less than {@link Bytes#SHORT} bytes are remaining in the buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param s The short to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#SHORT}.
+   * @see Buffer#writeUnsignedShort(int, int)
+   */
+  @Override
+  Buffer writeUnsignedShort(int s);
+
+  /**
+   * Writes a 16-bit signed integer to the buffer at the given offset.
+   * <p>
+   * The short will be written at the given offset. If there are less than {@link Bytes#SHORT} bytes remaining in the buffer
+   * then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the short.
+   * @param s      The short to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#SHORT}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeUnsignedShort(int)
+   */
+  @Override
+  Buffer writeUnsignedShort(int offset, int s);
+
+  /**
+   * Writes a 32-bit signed integer to the buffer at the current position.
+   * <p>
+   * When the integer is written to the buffer, the buffer's {@code position} will be advanced by {@link Bytes#INTEGER}.
+   * If less than {@link Bytes#INTEGER} bytes are remaining in the buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param i The integer to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#INTEGER}.
+   * @see Buffer#writeInt(int, int)
+   */
+  @Override
+  Buffer writeInt(int i);
+
+  /**
+   * Writes a 32-bit signed integer to the buffer at the given offset.
+   * <p>
+   * The integer will be written at the given offset. If there are less than {@link Bytes#INTEGER} bytes remaining
+   * in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the integer.
+   * @param i      The integer to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#INTEGER}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeInt(int)
+   */
+  @Override
+  Buffer writeInt(int offset, int i);
+
+  /**
+   * Writes a 32-bit signed integer to the buffer at the current position.
+   * <p>
+   * When the integer is written to the buffer, the buffer's {@code position} will be advanced by {@link Bytes#INTEGER}.
+   * If less than {@link Bytes#INTEGER} bytes are remaining in the buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param i The integer to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#INTEGER}.
+   * @see Buffer#writeUnsignedInt(int, long)
+   */
+  @Override
+  Buffer writeUnsignedInt(long i);
+
+  /**
+   * Writes a 32-bit signed integer to the buffer at the given offset.
+   * <p>
+   * The integer will be written at the given offset. If there are less than {@link Bytes#INTEGER} bytes remaining
+   * in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the integer.
+   * @param i      The integer to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#INTEGER}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeUnsignedInt(long)
+   */
+  @Override
+  Buffer writeUnsignedInt(int offset, long i);
+
+  /**
+   * Writes a 64-bit signed integer to the buffer at the current position.
+   * <p>
+   * When the long is written to the buffer, the buffer's {@code position} will be advanced by {@link Bytes#LONG}.
+   * If less than {@link Bytes#LONG} bytes are remaining in the buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param l The long to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#LONG}.
+   * @see Buffer#writeLong(int, long)
+   */
+  @Override
+  Buffer writeLong(long l);
+
+  /**
+   * Writes a 64-bit signed integer to the buffer at the given offset.
+   * <p>
+   * The long will be written at the given offset. If there are less than {@link Bytes#LONG} bytes remaining
+   * in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the long.
+   * @param l      The long to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#LONG}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeLong(long)
+   */
+  @Override
+  Buffer writeLong(int offset, long l);
+
+  /**
+   * Writes a single-precision 32-bit floating point number to the buffer at the current position.
+   * <p>
+   * When the float is written to the buffer, the buffer's {@code position} will be advanced by {@link Bytes#FLOAT}.
+   * If less than {@link Bytes#FLOAT} bytes are remaining in the buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param f The float to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#FLOAT}.
+   * @see Buffer#writeFloat(int, float)
+   */
+  @Override
+  Buffer writeFloat(float f);
+
+  /**
+   * Writes a single-precision 32-bit floating point number to the buffer at the given offset.
+   * <p>
+   * The float will be written at the given offset. If there are less than {@link Bytes#FLOAT} bytes remaining
+   * in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the float.
+   * @param f      The float to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#FLOAT}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeFloat(float)
+   */
+  @Override
+  Buffer writeFloat(int offset, float f);
+
+  /**
+   * Writes a double-precision 64-bit floating point number to the buffer at the current position.
+   * <p>
+   * When the double is written to the buffer, the buffer's {@code position} will be advanced by {@link Bytes#DOUBLE}.
+   * If less than {@link Bytes#DOUBLE} bytes are remaining in the buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param d The double to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#DOUBLE}.
+   * @see Buffer#writeDouble(int, double)
+   */
+  @Override
+  Buffer writeDouble(double d);
+
+  /**
+   * Writes a double-precision 64-bit floating point number to the buffer at the given offset.
+   * <p>
+   * The double will be written at the given offset. If there are less than {@link Bytes#DOUBLE} bytes remaining
+   * in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the double.
+   * @param d      The double to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@link Bytes#DOUBLE}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeDouble(double)
+   */
+  @Override
+  Buffer writeDouble(int offset, double d);
+
+  /**
+   * Writes a 1 byte boolean to the buffer at the current position.
+   * <p>
+   * When the boolean is written to the buffer, the buffer's {@code position} will be advanced by {@code 1}.
+   * If there are no bytes remaining in the buffer then a {@link java.nio.BufferOverflowException}
+   * will be thrown.
+   *
+   * @param b The boolean to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If the number of bytes exceeds the buffer's remaining bytes.
+   * @see Buffer#writeBoolean(int, boolean)
+   */
+  @Override
+  Buffer writeBoolean(boolean b);
+
+  /**
+   * Writes a 1 byte boolean to the buffer at the given offset.
+   * <p>
+   * The boolean will be written as a single byte at the given offset. If there are no bytes remaining in the buffer
+   * then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the boolean.
+   * @param b      The boolean to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@code 1}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeBoolean(boolean)
+   */
+  @Override
+  Buffer writeBoolean(int offset, boolean b);
+
+  /**
+   * Writes a UTF-8 string to the buffer at the current position.
+   * <p>
+   * The string will be written with a two-byte unsigned byte length followed by the UTF-8 bytes. If there are not enough
+   * bytes remaining in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param s The string to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If the number of bytes exceeds the buffer's remaining bytes.
+   * @see Buffer#writeUTF8(int, String)
+   */
+  @Override
+  Buffer writeUTF8(String s);
+
+  /**
+   * Writes a UTF-8 string to the buffer at the given offset.
+   * <p>
+   * The string will be written with a two-byte unsigned byte length followed by the UTF-8 bytes. If there are not enough
+   * bytes remaining in the buffer then a {@link java.nio.BufferOverflowException} will be thrown.
+   *
+   * @param offset The offset at which to write the string.
+   * @param s      The string to write.
+   * @return The written buffer.
+   * @throws java.nio.BufferOverflowException If {@link Buffer#remaining()} is less than {@code 1}.
+   * @throws IndexOutOfBoundsException        If the given offset is out of the bounds of the buffer. Note that
+   *                                          bounds are determined by the buffer's {@link Buffer#limit()} rather than capacity.
+   * @see Buffer#writeUTF8(String)
+   */
+  @Override
+  Buffer writeUTF8(int offset, String s);
+
+  /**
+   * Closes the buffer.
+   * <p>
+   * This method effectively acts as an alias to {@link Buffer#release()} and allows buffers to
+   * be used as resources in try-with-resources statements. When the buffer is closed the internal reference counter
+   * defined by {@link ReferenceCounted} will be decremented. However, if references to the
+   * buffer still exist then those references will be allowed to continue to operate on the buffer until all references
+   * have been released.
+   */
+  @Override
+  void close();
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferAllocator.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferAllocator.java
new file mode 100644 (file)
index 0000000..c9d3c54
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+/**
+ * Buffer allocator.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface BufferAllocator {
+
+  /**
+   * Allocates a dynamic capacity buffer.
+   *
+   * @return The allocated buffer.
+   */
+  Buffer allocate();
+
+  /**
+   * Allocates a dynamic capacity buffer with the given initial capacity.
+   *
+   * @param initialCapacity The initial buffer capacity.
+   * @return The allocated buffer.
+   */
+  Buffer allocate(int initialCapacity);
+
+  /**
+   * Allocates a new buffer.
+   *
+   * @param initialCapacity The initial buffer capacity.
+   * @param maxCapacity     The maximum buffer capacity.
+   * @return The allocated buffer.
+   */
+  Buffer allocate(int initialCapacity, int maxCapacity);
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferInput.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferInput.java
new file mode 100644 (file)
index 0000000..d33c360
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.charset.Charset;
+import java.util.function.Function;
+
+/**
+ * Readable buffer.
+ * <p>
+ * This interface exposes methods for reading from a byte buffer. Readable buffers maintain a small amount of state
+ * regarding current cursor positions and limits similar to the behavior of {@link java.nio.ByteBuffer}.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface BufferInput<T extends BufferInput<?>> extends AutoCloseable {
+
+  /**
+   * Returns the buffer's current read/write position.
+   * <p>
+   * The position is an internal cursor that tracks where to write/read bytes in the underlying storage implementation.
+   * As bytes are written to or read from the buffer, the position will advance based on the number of bytes read.
+   *
+   * @return The buffer's current position.
+   */
+  int position();
+
+  /**
+   * Returns the number of bytes remaining in the input.
+   *
+   * @return The number of bytes remaining in the input.
+   */
+  int remaining();
+
+  /**
+   * Returns a boolean value indicating whether the input has bytes remaining.
+   *
+   * @return Indicates whether bytes remain to be read from the input.
+   */
+  boolean hasRemaining();
+
+  /**
+   * Skips the given number of bytes in the input.
+   *
+   * @param bytes The number of bytes to attempt to skip.
+   * @return The skipped input.
+   */
+  T skip(int bytes);
+
+  /**
+   * Reads bytes into the given byte array.
+   *
+   * @param bytes The byte array into which to read bytes.
+   * @return The buffer.
+   */
+  T read(Bytes bytes);
+
+  /**
+   * Reads bytes into the given byte array.
+   *
+   * @param bytes The byte array into which to read bytes.
+   * @return The buffer.
+   */
+  T read(byte[] bytes);
+
+  /**
+   * Reads bytes into the given byte array starting at the current position.
+   *
+   * @param bytes  The byte array into which to read bytes.
+   * @param offset The offset at which to write bytes into the given buffer
+   * @return The buffer.
+   */
+  T read(Bytes bytes, int offset, int length);
+
+  /**
+   * Reads bytes into the given byte array starting at current position up to the given length.
+   *
+   * @param bytes  The byte array into which to read bytes.
+   * @param offset The offset at which to write bytes into the given buffer
+   * @return The buffer.
+   */
+  T read(byte[] bytes, int offset, int length);
+
+  /**
+   * Reads bytes into the given buffer.
+   *
+   * @param buffer The buffer into which to read bytes.
+   * @return The buffer.
+   */
+  T read(Buffer buffer);
+
+  /**
+   * Reads an object from the buffer.
+   *
+   * @param decoder the object decoder
+   * @param <U> the type of the object to read
+   * @return the read object.
+   */
+  default <U> U readObject(Function<byte[], U> decoder) {
+    byte[] bytes = readBytes(readInt());
+    return decoder.apply(bytes);
+  }
+
+  /**
+   * Reads a byte array.
+   *
+   * @param length The byte array length
+   * @return The read byte array.
+   */
+  default byte[] readBytes(int length) {
+    byte[] bytes = new byte[length];
+    read(bytes);
+    return bytes;
+  }
+
+  /**
+   * Reads a byte from the buffer at the current position.
+   *
+   * @return The read byte.
+   */
+  int readByte();
+
+  /**
+   * Reads an unsigned byte from the buffer at the current position.
+   *
+   * @return The read byte.
+   */
+  int readUnsignedByte();
+
+  /**
+   * Reads a 16-bit character from the buffer at the current position.
+   *
+   * @return The read character.
+   */
+  char readChar();
+
+  /**
+   * Reads a 16-bit signed integer from the buffer at the current position.
+   *
+   * @return The read short.
+   */
+  short readShort();
+
+  /**
+   * Reads a 16-bit unsigned integer from the buffer at the current position.
+   *
+   * @return The read short.
+   */
+  int readUnsignedShort();
+
+  /**
+   * Reads a 24-bit signed integer from the buffer at the current position.
+   *
+   * @return The read integer.
+   */
+  int readMedium();
+
+  /**
+   * Reads a 24-bit unsigned integer from the buffer at the current position.
+   *
+   * @return The read integer.
+   */
+  int readUnsignedMedium();
+
+  /**
+   * Reads a 32-bit signed integer from the buffer at the current position.
+   *
+   * @return The read integer.
+   */
+  int readInt();
+
+  /**
+   * Reads a 32-bit unsigned integer from the buffer at the current position.
+   *
+   * @return The read integer.
+   */
+  long readUnsignedInt();
+
+  /**
+   * Reads a 64-bit signed integer from the buffer at the current position.
+   *
+   * @return The read long.
+   */
+  long readLong();
+
+  /**
+   * Reads a single-precision 32-bit floating point number from the buffer at the current position.
+   *
+   * @return The read float.
+   */
+  float readFloat();
+
+  /**
+   * Reads a double-precision 64-bit floating point number from the buffer at the current position.
+   *
+   * @return The read double.
+   */
+  double readDouble();
+
+  /**
+   * Reads a 1 byte boolean from the buffer at the current position.
+   *
+   * @return The read boolean.
+   */
+  boolean readBoolean();
+
+  /**
+   * Reads a string from the buffer at the current position.
+   *
+   * @return The read string.
+   */
+  String readString();
+
+  /**
+   * Reads a string from the buffer at the current position.
+   *
+   * @param charset The character set with which to decode the string.
+   * @return The read string.
+   */
+  String readString(Charset charset);
+
+  /**
+   * Reads a UTF-8 string from the buffer at the current position.
+   *
+   * @return The read string.
+   */
+  String readUTF8();
+
+  @Override
+  void close();
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferOutput.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferOutput.java
new file mode 100644 (file)
index 0000000..722afb7
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.charset.Charset;
+import java.util.function.Function;
+
+/**
+ * Writable buffer.
+ * <p>
+ * This interface exposes methods for writing to a byte buffer. Writable buffers maintain a small amount of state
+ * regarding current cursor positions and limits similar to the behavior of {@link java.nio.ByteBuffer}.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface BufferOutput<T extends BufferOutput<?>> extends AutoCloseable {
+
+  /**
+   * Writes an array of bytes to the buffer.
+   *
+   * @param bytes The array of bytes to write.
+   * @return The written buffer.
+   */
+  T write(Bytes bytes);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   *
+   * @param bytes The array of bytes to write.
+   * @return The written buffer.
+   */
+  T write(byte[] bytes);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   *
+   * @param bytes  The array of bytes to write.
+   * @param offset The offset at which to start writing the bytes.
+   * @param length The number of bytes from the provided byte array to write to the buffer.
+   * @return The written buffer.
+   */
+  T write(Bytes bytes, int offset, int length);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   *
+   * @param bytes  The array of bytes to write.
+   * @param offset The offset at which to start writing the bytes.
+   * @param length The number of bytes from the provided byte array to write to the buffer.
+   * @return The written buffer.
+   */
+  T write(byte[] bytes, int offset, int length);
+
+  /**
+   * Writes a buffer to the buffer.
+   *
+   * @param buffer The buffer to write.
+   * @return The written buffer.
+   */
+  T write(Buffer buffer);
+
+  /**
+   * Writes an object to the snapshot.
+   *
+   * @param object the object to write
+   * @param encoder the object encoder
+   * @return The snapshot writer.
+   */
+  @SuppressWarnings("unchecked")
+  default <U> T writeObject(U object, Function<U, byte[]> encoder) {
+    byte[] bytes = encoder.apply(object);
+    writeInt(bytes.length).write(bytes);
+    return (T) this;
+  }
+
+  /**
+   * Writes a byte array.
+   *
+   * @param bytes The byte array to write.
+   * @return The written buffer.
+   */
+  @SuppressWarnings("unchecked")
+  default T writeBytes(byte[] bytes) {
+    write(bytes);
+    return (T) this;
+  }
+
+  /**
+   * Writes a byte to the buffer.
+   *
+   * @param b The byte to write.
+   * @return The written buffer.
+   */
+  T writeByte(int b);
+
+  /**
+   * Writes an unsigned byte to the buffer.
+   *
+   * @param b The byte to write.
+   * @return The written buffer.
+   */
+  T writeUnsignedByte(int b);
+
+  /**
+   * Writes a 16-bit character to the buffer.
+   *
+   * @param c The character to write.
+   * @return The written buffer.
+   */
+  T writeChar(char c);
+
+  /**
+   * Writes a 16-bit signed integer to the buffer.
+   *
+   * @param s The short to write.
+   * @return The written buffer.
+   */
+  T writeShort(short s);
+
+  /**
+   * Writes a 16-bit unsigned integer to the buffer.
+   *
+   * @param s The short to write.
+   * @return The written buffer.
+   */
+  T writeUnsignedShort(int s);
+
+  /**
+   * Writes a 24-bit signed integer to the buffer.
+   *
+   * @param m The integer to write.
+   * @return The written buffer.
+   */
+  T writeMedium(int m);
+
+  /**
+   * Writes a 24-bit unsigned integer to the buffer.
+   *
+   * @param m The integer to write.
+   * @return The written buffer.
+   */
+  T writeUnsignedMedium(int m);
+
+  /**
+   * Writes a 32-bit signed integer to the buffer.
+   *
+   * @param i The integer to write.
+   * @return The written buffer.
+   */
+  T writeInt(int i);
+
+  /**
+   * Writes a 32-bit unsigned integer to the buffer.
+   *
+   * @param i The integer to write.
+   * @return The written buffer.
+   */
+  T writeUnsignedInt(long i);
+
+  /**
+   * Writes a 64-bit signed integer to the buffer.
+   *
+   * @param l The long to write.
+   * @return The written buffer.
+   */
+  T writeLong(long l);
+
+  /**
+   * Writes a single-precision 32-bit floating point number to the buffer.
+   *
+   * @param f The float to write.
+   * @return The written buffer.
+   */
+  T writeFloat(float f);
+
+  /**
+   * Writes a double-precision 64-bit floating point number to the buffer.
+   *
+   * @param d The double to write.
+   * @return The written buffer.
+   */
+  T writeDouble(double d);
+
+  /**
+   * Writes a 1 byte boolean to the buffer.
+   *
+   * @param b The boolean to write.
+   * @return The written buffer.
+   */
+  T writeBoolean(boolean b);
+
+  /**
+   * Writes a string to the buffer.
+   *
+   * @param s The string to write.
+   * @return The written buffer.
+   */
+  T writeString(String s);
+
+  /**
+   * Writes a string to the buffer.
+   *
+   * @param s       The string to write.
+   * @param charset The character set with which to encode the string.
+   * @return The written buffer.
+   */
+  T writeString(String s, Charset charset);
+
+  /**
+   * Writes a UTF-8 string to the buffer.
+   *
+   * @param s The string to write.
+   * @return The written buffer.
+   */
+  T writeUTF8(String s);
+
+  /**
+   * Flushes the buffer to the underlying persistence layer.
+   *
+   * @return The flushed buffer.
+   */
+  T flush();
+
+  @Override
+  void close();
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferPool.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BufferPool.java
new file mode 100644 (file)
index 0000000..d5bc818
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.concurrent.ReferenceFactory;
+import io.atomix.utils.concurrent.ReferencePool;
+
+/**
+ * Buffer pool.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class BufferPool extends ReferencePool<Buffer> {
+
+  public BufferPool(ReferenceFactory<Buffer> factory) {
+    super(factory);
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/ByteBufferBuffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/ByteBufferBuffer.java
new file mode 100644 (file)
index 0000000..aa3e28e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.concurrent.ReferenceManager;
+
+/**
+ * {@link java.nio.ByteBuffer} based buffer.
+ */
+public abstract class ByteBufferBuffer extends AbstractBuffer {
+  protected final ByteBufferBytes bytes;
+
+  public ByteBufferBuffer(ByteBufferBytes bytes, ReferenceManager<Buffer> referenceManager) {
+    super(bytes, referenceManager);
+    this.bytes = bytes;
+  }
+
+  public ByteBufferBuffer(ByteBufferBytes bytes, int offset, int initialCapacity, int maxCapacity, ReferenceManager<Buffer> referenceManager) {
+    super(bytes, offset, initialCapacity, maxCapacity, referenceManager);
+    this.bytes = bytes;
+  }
+
+  @Override
+  public byte[] array() {
+    return bytes.array();
+  }
+
+  @Override
+  protected void compact(int from, int to, int length) {
+    byte[] bytes = new byte[1024];
+    int position = from;
+    while (position < from + length) {
+      int size = Math.min((from + length) - position, 1024);
+      this.bytes.read(position, bytes, 0, size);
+      this.bytes.write(0, bytes, 0, size);
+      position += size;
+    }
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/ByteBufferBytes.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/ByteBufferBytes.java
new file mode 100644 (file)
index 0000000..a3ed8b7
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Byte buffer bytes.
+ */
+public abstract class ByteBufferBytes extends AbstractBytes {
+  protected ByteBuffer buffer;
+
+  protected ByteBufferBytes(ByteBuffer buffer) {
+    this.buffer = buffer;
+  }
+
+  public Bytes reset(ByteBuffer buffer) {
+    buffer.clear();
+    this.buffer = checkNotNull(buffer, "buffer cannot be null");
+    return this;
+  }
+
+  /**
+   * Allocates a new byte buffer.
+   *
+   * @param size the buffer size
+   * @return a newly allocated byte buffer
+   */
+  protected abstract ByteBuffer newByteBuffer(int size);
+
+  @Override
+  public Bytes resize(int newSize) {
+    ByteBuffer oldBuffer = buffer;
+    ByteBuffer newBuffer = newByteBuffer(newSize);
+    oldBuffer.position(0).limit(oldBuffer.capacity());
+    newBuffer.position(0).limit(newBuffer.capacity());
+    newBuffer.put(oldBuffer);
+    newBuffer.clear();
+    return reset(newBuffer);
+  }
+
+  @Override
+  public byte[] array() {
+    return buffer.array();
+  }
+
+  /**
+   * Returns the underlying {@link ByteBuffer}.
+   *
+   * @return the underlying byte buffer
+   */
+  public ByteBuffer byteBuffer() {
+    return buffer;
+  }
+
+  @Override
+  public Bytes zero() {
+    return this;
+  }
+
+  @Override
+  public int size() {
+    return buffer.capacity();
+  }
+
+  @Override
+  public ByteOrder order() {
+    return buffer.order();
+  }
+
+  @Override
+  public Bytes order(ByteOrder order) {
+    return reset(buffer.order(order));
+  }
+
+  /**
+   * Returns the index for the given offset.
+   */
+  private int index(int offset) {
+    return (int) offset;
+  }
+
+  @Override
+  public Bytes zero(int offset) {
+    for (int i = index(offset); i < buffer.capacity(); i++) {
+      buffer.put(i, (byte) 0);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes zero(int offset, int length) {
+    for (int i = index(offset); i < offset + length; i++) {
+      buffer.put(i, (byte) 0);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes read(int position, byte[] bytes, int offset, int length) {
+    for (int i = 0; i < length; i++) {
+      bytes[index(offset) + i] = (byte) readByte(position + i);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes read(int position, Bytes bytes, int offset, int length) {
+    for (int i = 0; i < length; i++) {
+      bytes.writeByte(offset + i, readByte(position + i));
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes write(int position, byte[] bytes, int offset, int length) {
+    for (int i = 0; i < length; i++) {
+      buffer.put((int) position + i, (byte) bytes[index(offset) + i]);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes write(int position, Bytes bytes, int offset, int length) {
+    for (int i = 0; i < length; i++) {
+      buffer.put((int) position + i, (byte) bytes.readByte(offset + i));
+    }
+    return this;
+  }
+
+  @Override
+  public int readByte(int offset) {
+    return buffer.get(index(offset));
+  }
+
+  @Override
+  public char readChar(int offset) {
+    return buffer.getChar(index(offset));
+  }
+
+  @Override
+  public short readShort(int offset) {
+    return buffer.getShort(index(offset));
+  }
+
+  @Override
+  public int readInt(int offset) {
+    return buffer.getInt(index(offset));
+  }
+
+  @Override
+  public long readLong(int offset) {
+    return buffer.getLong(index(offset));
+  }
+
+  @Override
+  public float readFloat(int offset) {
+    return buffer.getFloat(index(offset));
+  }
+
+  @Override
+  public double readDouble(int offset) {
+    return buffer.getDouble(index(offset));
+  }
+
+  @Override
+  public Bytes writeByte(int offset, int b) {
+    buffer.put(index(offset), (byte) b);
+    return this;
+  }
+
+  @Override
+  public Bytes writeChar(int offset, char c) {
+    buffer.putChar(index(offset), c);
+    return this;
+  }
+
+  @Override
+  public Bytes writeShort(int offset, short s) {
+    buffer.putShort(index(offset), s);
+    return this;
+  }
+
+  @Override
+  public Bytes writeInt(int offset, int i) {
+    buffer.putInt(index(offset), i);
+    return this;
+  }
+
+  @Override
+  public Bytes writeLong(int offset, long l) {
+    buffer.putLong(index(offset), l);
+    return this;
+  }
+
+  @Override
+  public Bytes writeFloat(int offset, float f) {
+    buffer.putFloat(index(offset), f);
+    return this;
+  }
+
+  @Override
+  public Bytes writeDouble(int offset, double d) {
+    buffer.putDouble(index(offset), d);
+    return this;
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/Bytes.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/Bytes.java
new file mode 100644 (file)
index 0000000..7a28b99
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.ByteOrder;
+
+/**
+ * Common interface for interacting with a memory or disk based array of bytes.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface Bytes extends BytesInput<Bytes>, BytesOutput<Bytes>, AutoCloseable {
+  int BYTE = 1;
+  int BOOLEAN = 1;
+  int CHARACTER = 2;
+  int SHORT = 2;
+  int MEDIUM = 3;
+  int INTEGER = 4;
+  int LONG = 8;
+  int FLOAT = 4;
+  int DOUBLE = 8;
+
+  /**
+   * Returns whether the bytes has an array.
+   *
+   * @return Whether the bytes has an underlying array.
+   */
+  default boolean hasArray() {
+    return false;
+  }
+
+  /**
+   * Returns the underlying byte array.
+   *
+   * @return the underlying byte array
+   * @throws UnsupportedOperationException if a heap array is not supported
+   */
+  default byte[] array() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Returns the count of the bytes.
+   *
+   * @return The count of the bytes.
+   */
+  int size();
+
+  /**
+   * Resizes the bytes.
+   * <p>
+   * When the bytes are resized, underlying memory addresses in copies of this instance may no longer be valid. Additionally,
+   * if the {@code newSize} is smaller than the current {@code count} then some data may be lost during the resize. Use
+   * with caution.
+   *
+   * @param newSize The count to which to resize this instance.
+   * @return The resized bytes.
+   */
+  Bytes resize(int newSize);
+
+  /**
+   * Returns the byte order.
+   * <p>
+   * For consistency with {@link java.nio.ByteBuffer}, all bytes implementations are initially in {@link ByteOrder#BIG_ENDIAN} order.
+   *
+   * @return The byte order.
+   */
+  ByteOrder order();
+
+  /**
+   * Sets the byte order, returning a new swapped {@link Bytes} instance.
+   * <p>
+   * By default, all bytes are read and written in {@link ByteOrder#BIG_ENDIAN} order. This provides complete
+   * consistency with {@link java.nio.ByteBuffer}. To flip bytes to {@link ByteOrder#LITTLE_ENDIAN} order, this
+   * {@code Bytes} instance is decorated by a {@link SwappedBytes} instance which will reverse
+   * read and written bytes using, e.g. {@link Integer#reverseBytes(int)}.
+   *
+   * @param order The byte order.
+   * @return The updated bytes.
+   * @throws NullPointerException If the {@code order} is {@code null}
+   */
+  Bytes order(ByteOrder order);
+
+  /**
+   * Returns a boolean value indicating whether the bytes are direct.
+   *
+   * @return Indicates whether the bytes are direct.
+   */
+  boolean isDirect();
+
+  /**
+   * Returns a boolean value indicating whether the bytes are backed by a file.
+   *
+   * @return Indicates whether the bytes are backed by a file.
+   */
+  boolean isFile();
+
+  @Override
+  void close();
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BytesInput.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BytesInput.java
new file mode 100644 (file)
index 0000000..f097b13
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.charset.Charset;
+
+/**
+ * Readable bytes.
+ * <p>
+ * This interface exposes methods for reading bytes from specific positions in a byte array.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface BytesInput<T extends BytesInput<T>> {
+
+  /**
+   * Reads bytes into the given byte array starting at the given offset up to the given length.
+   *
+   * @param offset    The offset from which to start reading bytes.
+   * @param dst       The byte array into which to read bytes.
+   * @param dstOffset The offset at which to write bytes into the given buffer.
+   * @param length    The total number of bytes to read.
+   * @return The buffer.
+   */
+  T read(int offset, Bytes dst, int dstOffset, int length);
+
+  /**
+   * Reads bytes into the given byte array starting at the given offset up to the given length.
+   *
+   * @param offset    The offset from which to start reading bytes.
+   * @param dst       The byte array into which to read bytes.
+   * @param dstOffset The offset at which to write bytes into the given buffer
+   * @param length    The total number of bytes to read.
+   * @return The buffer.
+   */
+  T read(int offset, byte[] dst, int dstOffset, int length);
+
+  /**
+   * Reads a byte from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the byte.
+   * @return The read byte.
+   */
+  int readByte(int offset);
+
+  /**
+   * Reads an unsigned byte from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the byte.
+   * @return The read unsigned byte.
+   */
+  int readUnsignedByte(int offset);
+
+  /**
+   * Reads a 16-bit character from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the character.
+   * @return The read character.
+   */
+  char readChar(int offset);
+
+  /**
+   * Reads a 16-bit signed integer from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the short.
+   * @return The read short.
+   */
+  short readShort(int offset);
+
+  /**
+   * Reads a 16-bit unsigned integer from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the short.
+   * @return The read short.
+   */
+  int readUnsignedShort(int offset);
+
+  /**
+   * Reads a 24-bit signed integer from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the integer.
+   * @return The read medium.
+   */
+  int readMedium(int offset);
+
+  /**
+   * Reads a 24-bin unsigned integer from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the integer.
+   * @return The read medium.
+   */
+  int readUnsignedMedium(int offset);
+
+  /**
+   * Reads a 32-bit signed integer from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the integer.
+   * @return The read integer.
+   */
+  int readInt(int offset);
+
+  /**
+   * Reads a 32-bit unsigned integer from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the integer.
+   * @return The read integer.
+   */
+  long readUnsignedInt(int offset);
+
+  /**
+   * Reads a 64-bit signed integer from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the long.
+   * @return The read long.
+   */
+  long readLong(int offset);
+
+  /**
+   * Reads a single-precision 32-bit floating point number from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the float.
+   * @return The read float.
+   */
+  float readFloat(int offset);
+
+  /**
+   * Reads a double-precision 64-bit floating point number from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the double.
+   * @return The read double.
+   */
+  double readDouble(int offset);
+
+  /**
+   * Reads a 1 byte boolean from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the boolean.
+   * @return The read boolean.
+   */
+  boolean readBoolean(int offset);
+
+  /**
+   * Reads a string from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the string.
+   * @return The read string.
+   */
+  String readString(int offset);
+
+  /**
+   * Reads a string from the buffer at the given offset.
+   *
+   * @param offset  The offset at which to read the string.
+   * @param charset The character set with which to decode the string.
+   * @return The read string.
+   */
+  String readString(int offset, Charset charset);
+
+  /**
+   * Reads a UTF-8 string from the buffer at the given offset.
+   *
+   * @param offset The offset at which to read the string.
+   * @return The read string.
+   */
+  String readUTF8(int offset);
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BytesOutput.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/BytesOutput.java
new file mode 100644 (file)
index 0000000..30673b5
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.charset.Charset;
+
+/**
+ * Writable bytes.
+ * <p>
+ * This interface exposes methods for writing bytes to specific positions in a byte array.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface BytesOutput<T extends BytesOutput<T>> {
+
+  /**
+   * Zeros out all bytes in the array.
+   *
+   * @return The written bytes.
+   */
+  T zero();
+
+  /**
+   * Zeros out all bytes starting at the given offset in the array.
+   *
+   * @param offset The offset at which to start zeroing out bytes.
+   * @return The written bytes.
+   */
+  T zero(int offset);
+
+  /**
+   * Zeros out bytes starting at the given offset up to the given length.
+   *
+   * @param offset The offset at which to start zeroing out bytes.
+   * @param length THe total number of bytes to zero out.
+   * @return The written bytes.
+   */
+  T zero(int offset, int length);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   *
+   * @param offset    The offset at which to start writing the bytes.
+   * @param src       The array of bytes to write.
+   * @param srcOffset The offset at which to start reading bytes from the given source.
+   * @param length    The number of bytes from the provided byte array to write to the buffer.
+   * @return The written buffer.
+   */
+  T write(int offset, Bytes src, int srcOffset, int length);
+
+  /**
+   * Writes an array of bytes to the buffer.
+   *
+   * @param offset    The offset at which to start writing the bytes.
+   * @param src       The array of bytes to write.
+   * @param srcOffset The offset at which to start reading bytes from the given source.
+   * @param length    The number of bytes from the provided byte array to write to the buffer.
+   * @return The written buffer.
+   */
+  T write(int offset, byte[] src, int srcOffset, int length);
+
+  /**
+   * Writes a byte to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the byte.
+   * @param b      The byte to write.
+   * @return The written buffer.
+   */
+  T writeByte(int offset, int b);
+
+  /**
+   * Writes an unsigned byte to the buffer at the given position.
+   *
+   * @param offset The offset at which to write the byte.
+   * @param b      The byte to write.
+   * @return The written buffer.
+   */
+  T writeUnsignedByte(int offset, int b);
+
+  /**
+   * Writes a 16-bit character to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the character.
+   * @param c      The character to write.
+   * @return The written buffer.
+   */
+  T writeChar(int offset, char c);
+
+  /**
+   * Writes a 16-bit signed integer to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the short.
+   * @param s      The short to write.
+   * @return The written buffer.
+   */
+  T writeShort(int offset, short s);
+
+  /**
+   * Writes a 16-bit unsigned integer to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the short.
+   * @param s      The short to write.
+   * @return The written buffer.
+   */
+  T writeUnsignedShort(int offset, int s);
+
+  /**
+   * Writes a 24-bit signed integer to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the short.
+   * @param m      The short to write.
+   * @return The written buffer.
+   */
+  T writeMedium(int offset, int m);
+
+  /**
+   * Writes a 24-bit unsigned integer to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the short.
+   * @param m      The short to write.
+   * @return The written buffer.
+   */
+  T writeUnsignedMedium(int offset, int m);
+
+  /**
+   * Writes a 32-bit signed integer to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the integer.
+   * @param i      The integer to write.
+   * @return The written buffer.
+   */
+  T writeInt(int offset, int i);
+
+  /**
+   * Writes a 32-bit unsigned integer to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the integer.
+   * @param i      The integer to write.
+   * @return The written buffer.
+   */
+  T writeUnsignedInt(int offset, long i);
+
+  /**
+   * Writes a 64-bit signed integer to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the long.
+   * @param l      The long to write.
+   * @return The written buffer.
+   */
+  T writeLong(int offset, long l);
+
+  /**
+   * Writes a single-precision 32-bit floating point number to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the float.
+   * @param f      The float to write.
+   * @return The written buffer.
+   */
+  T writeFloat(int offset, float f);
+
+  /**
+   * Writes a double-precision 64-bit floating point number to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the double.
+   * @param d      The double to write.
+   * @return The written buffer.
+   */
+  T writeDouble(int offset, double d);
+
+  /**
+   * Writes a 1 byte boolean to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the boolean.
+   * @param b      The boolean to write.
+   * @return The written buffer.
+   */
+  T writeBoolean(int offset, boolean b);
+
+  /**
+   * Writes a string to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the string.
+   * @param s      The string to write.
+   * @return The written buffer.
+   */
+  T writeString(int offset, String s);
+
+  /**
+   * Writes a string to the buffer at the given offset.
+   *
+   * @param offset  The offset at which to write the string.
+   * @param s       The string to write.
+   * @param charset The character set with which to encode the string.
+   * @return The written buffer.
+   */
+  T writeString(int offset, String s, Charset charset);
+
+  /**
+   * Writes a UTF-8 string to the buffer at the given offset.
+   *
+   * @param offset The offset at which to write the string.
+   * @param s      The string to write.
+   * @return The written buffer.
+   */
+  T writeUTF8(int offset, String s);
+
+  /**
+   * Flushes the bytes to the underlying persistence layer.
+   *
+   * @return The flushed buffer.
+   */
+  T flush();
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/DirectBuffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/DirectBuffer.java
new file mode 100644 (file)
index 0000000..32cbd17
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.memory.Memory;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Direct {@link java.nio.ByteBuffer} based buffer.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class DirectBuffer extends ByteBufferBuffer {
+
+  /**
+   * Allocates a direct buffer with an initial capacity of {@code 4096} and a maximum capacity of {@link Long#MAX_VALUE}.
+   *
+   * @return The direct buffer.
+   * @see DirectBuffer#allocate(int)
+   * @see DirectBuffer#allocate(int, int)
+   */
+  public static DirectBuffer allocate() {
+    return allocate(DEFAULT_INITIAL_CAPACITY, MAX_SIZE);
+  }
+
+  /**
+   * Allocates a direct buffer with the given initial capacity.
+   *
+   * @param initialCapacity The initial capacity of the buffer to allocate (in bytes).
+   * @return The direct buffer.
+   * @throws IllegalArgumentException If {@code capacity} is greater than the maximum allowed count for
+   *                                  a {@link java.nio.ByteBuffer} - {@code Integer.MAX_VALUE - 5}
+   * @see DirectBuffer#allocate()
+   * @see DirectBuffer#allocate(int, int)
+   */
+  public static DirectBuffer allocate(int initialCapacity) {
+    return allocate(initialCapacity, MAX_SIZE);
+  }
+
+  /**
+   * Allocates a new direct buffer.
+   *
+   * @param initialCapacity The initial capacity of the buffer to allocate (in bytes).
+   * @param maxCapacity     The maximum capacity of the buffer.
+   * @return The direct buffer.
+   * @throws IllegalArgumentException If {@code capacity} or {@code maxCapacity} is greater than the maximum
+   *                                  allowed count for a {@link java.nio.ByteBuffer} - {@code Integer.MAX_VALUE - 5}
+   * @see DirectBuffer#allocate()
+   * @see DirectBuffer#allocate(int)
+   */
+  public static DirectBuffer allocate(int initialCapacity, int maxCapacity) {
+    checkArgument(initialCapacity <= maxCapacity, "initial capacity cannot be greater than maximum capacity");
+    return new DirectBuffer(DirectBytes.allocate((int) Math.min(Memory.Util.toPow2(initialCapacity), MAX_SIZE)), 0, initialCapacity, maxCapacity);
+  }
+
+  protected DirectBuffer(DirectBytes bytes, int offset, int initialCapacity, int maxCapacity) {
+    super(bytes, offset, initialCapacity, maxCapacity, null);
+  }
+
+  @Override
+  public DirectBuffer duplicate() {
+    return new DirectBuffer((DirectBytes) bytes, offset(), capacity(), maxCapacity());
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/DirectBytes.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/DirectBytes.java
new file mode 100644 (file)
index 0000000..2256f24
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * {@link ByteBuffer} based direct bytes.
+ */
+public class DirectBytes extends ByteBufferBytes {
+
+  /**
+   * Allocates a new direct byte array.
+   *
+   * @param size The count of the buffer to allocate (in bytes).
+   * @return The direct buffer.
+   * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed count for
+   *                                  an array on the Java heap - {@code Integer.MAX_VALUE - 5}
+   */
+  public static DirectBytes allocate(int size) {
+    if (size > MAX_SIZE) {
+      throw new IllegalArgumentException("size cannot for DirectBytes cannot be greater than " + MAX_SIZE);
+    }
+    return new DirectBytes(ByteBuffer.allocateDirect((int) size));
+  }
+
+  protected DirectBytes(ByteBuffer buffer) {
+    super(buffer);
+  }
+
+  @Override
+  protected ByteBuffer newByteBuffer(int size) {
+    return ByteBuffer.allocateDirect((int) size);
+  }
+
+  @Override
+  public boolean isDirect() {
+    return buffer.isDirect();
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/FileBuffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/FileBuffer.java
new file mode 100644 (file)
index 0000000..c375311
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.memory.Memory;
+
+import java.io.File;
+import java.nio.channels.FileChannel;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * File buffer.
+ * <p>
+ * File buffers wrap a simple {@link java.io.RandomAccessFile} instance to provide random access to a file on local disk. All
+ * operations are delegated directly to the {@link java.io.RandomAccessFile} interface, and limitations are dependent on the
+ * semantics of the underlying file.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class FileBuffer extends AbstractBuffer {
+
+  /**
+   * Allocates a file buffer of unlimited capacity.
+   * <p>
+   * The buffer will initially be allocated with {@code 4096} bytes. As bytes are written to the resulting buffer and
+   * the original capacity is reached, the buffer's capacity will double.
+   *
+   * @param file The file to allocate.
+   * @return The allocated buffer.
+   * @see FileBuffer#allocate(File, int)
+   * @see FileBuffer#allocate(File, int, int)
+   * @see FileBuffer#allocate(File, String, int, int)
+   */
+  public static FileBuffer allocate(File file) {
+    return allocate(file, FileBytes.DEFAULT_MODE, DEFAULT_INITIAL_CAPACITY, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Allocates a file buffer with the given initial capacity.
+   * <p>
+   * If the underlying file is empty, the file count will expand dynamically as bytes are written to the file.
+   * The underlying {@link FileBytes} will be initialized to the nearest power of {@code 2}.
+   *
+   * @param file            The file to allocate.
+   * @param initialCapacity The initial capacity of the bytes to allocate.
+   * @return The allocated buffer.
+   * @see FileBuffer#allocate(File)
+   * @see FileBuffer#allocate(File, int, int)
+   * @see FileBuffer#allocate(File, String, int, int)
+   */
+  public static FileBuffer allocate(File file, int initialCapacity) {
+    return allocate(file, FileBytes.DEFAULT_MODE, initialCapacity, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Allocates a file buffer.
+   * <p>
+   * The underlying {@link java.io.RandomAccessFile} will be created in {@code rw} mode by default.
+   * The resulting buffer will be initialized with a capacity of {@code initialCapacity}. The underlying {@link FileBytes}
+   * will be initialized to the nearest power of {@code 2}. As bytes are written to the file the buffer's capacity will
+   * double up to {@code maxCapacity}.
+   *
+   * @param file            The file to allocate.
+   * @param initialCapacity The initial capacity of the buffer.
+   * @param maxCapacity     The maximum allowed capacity of the buffer.
+   * @return The allocated buffer.
+   * @see FileBuffer#allocate(File)
+   * @see FileBuffer#allocate(File, int)
+   * @see FileBuffer#allocate(File, String, int, int)
+   */
+  public static FileBuffer allocate(File file, int initialCapacity, int maxCapacity) {
+    return allocate(file, FileBytes.DEFAULT_MODE, initialCapacity, maxCapacity);
+  }
+
+  /**
+   * Allocates a file buffer.
+   * <p>
+   * The resulting buffer will be initialized with a capacity of {@code initialCapacity}. The underlying {@link FileBytes}
+   * will be initialized to the nearest power of {@code 2}. As bytes are written to the file the buffer's capacity will
+   * double up to {@code maxCapacity}.
+   *
+   * @param file            The file to allocate.
+   * @param mode            The mode in which to open the underlying {@link java.io.RandomAccessFile}.
+   * @param initialCapacity The initial capacity of the buffer.
+   * @param maxCapacity     The maximum allowed capacity of the buffer.
+   * @return The allocated buffer.
+   * @see FileBuffer#allocate(File)
+   * @see FileBuffer#allocate(File, int)
+   * @see FileBuffer#allocate(File, int, int)
+   */
+  public static FileBuffer allocate(File file, String mode, int initialCapacity, int maxCapacity) {
+    checkArgument(initialCapacity <= maxCapacity, "initial capacity cannot be greater than maximum capacity");
+    return new FileBuffer(new FileBytes(file, mode, (int) Math.min(Memory.Util.toPow2(initialCapacity), maxCapacity)), 0, initialCapacity, maxCapacity);
+  }
+
+  private final FileBytes bytes;
+
+  private FileBuffer(FileBytes bytes, int offset, int initialCapacity, int maxCapacity) {
+    super(bytes, offset, initialCapacity, maxCapacity, null);
+    this.bytes = bytes;
+  }
+
+  /**
+   * Returns the underlying file object.
+   *
+   * @return The underlying file.
+   */
+  public File file() {
+    return ((FileBytes) bytes).file();
+  }
+
+  /**
+   * Maps a portion of the underlying file into memory in {@link FileChannel.MapMode#READ_WRITE} mode
+   * starting at the current position up to the given {@code count}.
+   *
+   * @param size The count of the bytes to map into memory.
+   * @return The mapped buffer.
+   * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed
+   *                                  {@link java.nio.MappedByteBuffer} count: {@link Integer#MAX_VALUE}
+   */
+  public MappedBuffer map(int size) {
+    return map(position(), size, FileChannel.MapMode.READ_WRITE);
+  }
+
+  /**
+   * Maps a portion of the underlying file into memory starting at the current position up to the given {@code count}.
+   *
+   * @param size The count of the bytes to map into memory.
+   * @param mode The mode in which to map the bytes into memory.
+   * @return The mapped buffer.
+   * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed
+   *                                  {@link java.nio.MappedByteBuffer} count: {@link Integer#MAX_VALUE}
+   */
+  public MappedBuffer map(int size, FileChannel.MapMode mode) {
+    return map(position(), size, mode);
+  }
+
+  /**
+   * Maps a portion of the underlying file into memory in {@link FileChannel.MapMode#READ_WRITE} mode
+   * starting at the given {@code offset} up to the given {@code count}.
+   *
+   * @param offset The offset from which to map bytes into memory.
+   * @param size   The count of the bytes to map into memory.
+   * @return The mapped buffer.
+   * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed
+   *                                  {@link java.nio.MappedByteBuffer} count: {@link Integer#MAX_VALUE}
+   */
+  public MappedBuffer map(int offset, int size) {
+    return map(offset, size, FileChannel.MapMode.READ_WRITE);
+  }
+
+  /**
+   * Maps a portion of the underlying file into memory starting at the given {@code offset} up to the given {@code count}.
+   *
+   * @param offset The offset from which to map bytes into memory.
+   * @param size   The count of the bytes to map into memory.
+   * @param mode   The mode in which to map the bytes into memory.
+   * @return The mapped buffer.
+   * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed
+   *                                  {@link java.nio.MappedByteBuffer} count: {@link Integer#MAX_VALUE}
+   */
+  public MappedBuffer map(int offset, int size, FileChannel.MapMode mode) {
+    return new MappedBuffer(((FileBytes) bytes).map(offset, size, mode), 0, size, size);
+  }
+
+  @Override
+  protected void compact(int from, int to, int length) {
+    byte[] bytes = new byte[1024];
+    int position = from;
+    while (position < from + length) {
+      int size = Math.min((from + length) - position, 1024);
+      this.bytes.read(position, bytes, 0, size);
+      this.bytes.write(0, bytes, 0, size);
+      position += size;
+    }
+  }
+
+  @Override
+  public FileBuffer duplicate() {
+    return new FileBuffer(new FileBytes(bytes.file(), bytes.mode(), bytes.size()), offset(), capacity(), maxCapacity());
+  }
+
+  /**
+   * Duplicates the buffer using the given mode.
+   *
+   * @return The mode with which to open the duplicate buffer.
+   */
+  public FileBuffer duplicate(String mode) {
+    return new FileBuffer(new FileBytes(bytes.file(), mode, bytes.size()), offset(), capacity(), maxCapacity());
+  }
+
+  /**
+   * Deletes the underlying file.
+   */
+  public void delete() {
+    bytes.delete();
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/FileBytes.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/FileBytes.java
new file mode 100644 (file)
index 0000000..326e8ed
--- /dev/null
@@ -0,0 +1,531 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.memory.Memory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+
+/**
+ * File bytes.
+ * <p>
+ * File bytes wrap a simple {@link RandomAccessFile} instance to provide random access to a randomAccessFile on local disk. All
+ * operations are delegated directly to the {@link RandomAccessFile} interface, and limitations are dependent on the
+ * semantics of the underlying randomAccessFile.
+ * <p>
+ * Bytes are always stored in the underlying randomAccessFile in {@link ByteOrder#BIG_ENDIAN} order.
+ * To flip the byte order to read or write to/from a randomAccessFile in {@link ByteOrder#LITTLE_ENDIAN} order use
+ * {@link Bytes#order(ByteOrder)}.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class FileBytes extends AbstractBytes {
+  static final String DEFAULT_MODE = "rw";
+
+  /**
+   * Allocates a randomAccessFile buffer of unlimited count.
+   * <p>
+   * The buffer will be allocated with {@link Long#MAX_VALUE} bytes. As bytes are written to the buffer, the underlying
+   * {@link RandomAccessFile} will expand.
+   *
+   * @param file The randomAccessFile to allocate.
+   * @return The allocated buffer.
+   */
+  public static FileBytes allocate(File file) {
+    return allocate(file, DEFAULT_MODE, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Allocates a randomAccessFile buffer.
+   * <p>
+   * If the underlying randomAccessFile is empty, the randomAccessFile count will expand dynamically as bytes are written to the randomAccessFile.
+   *
+   * @param file The randomAccessFile to allocate.
+   * @param size The count of the bytes to allocate.
+   * @return The allocated buffer.
+   */
+  public static FileBytes allocate(File file, int size) {
+    return allocate(file, DEFAULT_MODE, size);
+  }
+
+  /**
+   * Allocates a randomAccessFile buffer.
+   * <p>
+   * If the underlying randomAccessFile is empty, the randomAccessFile count will expand dynamically as bytes are written to the randomAccessFile.
+   *
+   * @param file The randomAccessFile to allocate.
+   * @param mode The mode in which to open the underlying {@link RandomAccessFile}.
+   * @param size The count of the bytes to allocate.
+   * @return The allocated buffer.
+   */
+  public static FileBytes allocate(File file, String mode, int size) {
+    return new FileBytes(file, mode, (int) Math.min(Memory.Util.toPow2(size), Integer.MAX_VALUE));
+  }
+
+  private static final int PAGE_SIZE = 1024 * 4;
+  private static final byte[] BLANK_PAGE = new byte[PAGE_SIZE];
+
+  private final File file;
+  private final String mode;
+  private final RandomAccessFile randomAccessFile;
+  private int size;
+
+  FileBytes(File file, String mode, int size) {
+    if (file == null) {
+      throw new NullPointerException("file cannot be null");
+    }
+    if (mode == null) {
+      mode = DEFAULT_MODE;
+    }
+    if (size < 0) {
+      throw new IllegalArgumentException("size must be positive");
+    }
+
+    this.file = file;
+    this.mode = mode;
+    this.size = size;
+    try {
+      this.randomAccessFile = new RandomAccessFile(file, mode);
+      if (size > randomAccessFile.length()) {
+        randomAccessFile.setLength(size);
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Returns the underlying file object.
+   *
+   * @return The underlying file.
+   */
+  public File file() {
+    return file;
+  }
+
+  /**
+   * Returns the file mode.
+   *
+   * @return The file mode.
+   */
+  public String mode() {
+    return mode;
+  }
+
+  @Override
+  public int size() {
+    return size;
+  }
+
+  @Override
+  public Bytes resize(int newSize) {
+    if (newSize < size) {
+      throw new IllegalArgumentException("cannot decrease file bytes size; use zero() to decrease file size");
+    }
+    int oldSize = this.size;
+    this.size = newSize;
+    try {
+      long length = randomAccessFile.length();
+      if (newSize > length) {
+        randomAccessFile.setLength(newSize);
+        zero(oldSize);
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public boolean isFile() {
+    return true;
+  }
+
+  /**
+   * Maps a portion of the randomAccessFile into memory in {@link FileChannel.MapMode#READ_WRITE} mode and returns
+   * a {@link UnsafeMappedBytes} instance.
+   *
+   * @param offset The offset from which to map the randomAccessFile into memory.
+   * @param size   The count of the bytes to map into memory.
+   * @return The mapped bytes.
+   * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed
+   *                                  {@link java.nio.MappedByteBuffer} count: {@link Integer#MAX_VALUE}
+   */
+  public MappedBytes map(int offset, int size) {
+    return map(offset, size, parseMode(mode));
+  }
+
+  /**
+   * Maps a portion of the randomAccessFile into memory and returns a {@link UnsafeMappedBytes} instance.
+   *
+   * @param offset The offset from which to map the randomAccessFile into memory.
+   * @param size   The count of the bytes to map into memory.
+   * @param mode   The mode in which to map the randomAccessFile into memory.
+   * @return The mapped bytes.
+   * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed
+   *                                  {@link java.nio.MappedByteBuffer} count: {@link Integer#MAX_VALUE}
+   */
+  public MappedBytes map(int offset, int size, FileChannel.MapMode mode) {
+    MappedByteBuffer mappedByteBuffer = mapFile(randomAccessFile, offset, size, mode);
+    return new MappedBytes(file, randomAccessFile, mappedByteBuffer, mode);
+  }
+
+  private static MappedByteBuffer mapFile(RandomAccessFile randomAccessFile, int offset, int size, FileChannel.MapMode mode) {
+    try {
+      return randomAccessFile.getChannel().map(mode, offset, size);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static FileChannel.MapMode parseMode(String mode) {
+    switch (mode) {
+      case "r":
+        return FileChannel.MapMode.READ_ONLY;
+      case "rw":
+      default:
+        return FileChannel.MapMode.READ_WRITE;
+    }
+  }
+
+  @Override
+  public ByteOrder order() {
+    return ByteOrder.BIG_ENDIAN;
+  }
+
+  /**
+   * Seeks to the given offset.
+   */
+  private void seekToOffset(int offset) throws IOException {
+    if (randomAccessFile.getFilePointer() != offset) {
+      randomAccessFile.seek(offset);
+    }
+  }
+
+  @Override
+  public Bytes zero() {
+    try {
+      randomAccessFile.setLength(0);
+      randomAccessFile.setLength(size);
+      for (int i = 0; i < size; i += PAGE_SIZE) {
+        randomAccessFile.write(BLANK_PAGE, 0, Math.min(size - i, PAGE_SIZE));
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes zero(int offset) {
+    try {
+      int length = Math.max(offset, size);
+      randomAccessFile.setLength(offset);
+      randomAccessFile.setLength(length);
+      seekToOffset(offset);
+      for (int i = offset; i < length; i += PAGE_SIZE) {
+        randomAccessFile.write(BLANK_PAGE, 0, Math.min(length - i, PAGE_SIZE));
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes zero(int offset, int length) {
+    for (int i = offset; i < offset + length; i++) {
+      writeByte(i, (byte) 0);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes read(int position, Bytes bytes, int offset, int length) {
+    checkRead(position, length);
+    if (bytes instanceof WrappedBytes) {
+      bytes = ((WrappedBytes) bytes).root();
+    }
+    if (bytes.hasArray()) {
+      try {
+        seekToOffset(position);
+        randomAccessFile.readFully(bytes.array(), (int) offset, (int) length);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    } else {
+      try {
+        seekToOffset(position);
+        byte[] readBytes = new byte[(int) length];
+        randomAccessFile.readFully(readBytes);
+        bytes.write(offset, readBytes, 0, length);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes read(int position, byte[] bytes, int offset, int length) {
+    checkRead(position, length);
+    try {
+      seekToOffset(position);
+      randomAccessFile.readFully(bytes, (int) offset, (int) length);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public int readByte(int offset) {
+    checkRead(offset, BYTE);
+    try {
+      seekToOffset(offset);
+      return randomAccessFile.readByte();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public char readChar(int offset) {
+    checkRead(offset, CHARACTER);
+    try {
+      seekToOffset(offset);
+      return randomAccessFile.readChar();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public short readShort(int offset) {
+    checkRead(offset, SHORT);
+    try {
+      seekToOffset(offset);
+      return randomAccessFile.readShort();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public int readInt(int offset) {
+    checkRead(offset, INTEGER);
+    try {
+      seekToOffset(offset);
+      return randomAccessFile.readInt();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public long readLong(int offset) {
+    checkRead(offset, LONG);
+    try {
+      seekToOffset(offset);
+      return randomAccessFile.readLong();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public float readFloat(int offset) {
+    checkRead(offset, FLOAT);
+    try {
+      seekToOffset(offset);
+      return randomAccessFile.readFloat();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public double readDouble(int offset) {
+    checkRead(offset, DOUBLE);
+    try {
+      seekToOffset(offset);
+      return randomAccessFile.readDouble();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public Bytes write(int position, Bytes bytes, int offset, int length) {
+    checkWrite(position, length);
+    if (bytes instanceof WrappedBytes) {
+      bytes = ((WrappedBytes) bytes).root();
+    }
+    if (bytes.hasArray()) {
+      try {
+        seekToOffset(position);
+        randomAccessFile.write(bytes.array(), (int) offset, (int) length);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    } else {
+      try {
+        seekToOffset(position);
+        byte[] writeBytes = new byte[(int) length];
+        bytes.read(offset, writeBytes, 0, length);
+        randomAccessFile.write(writeBytes);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes write(int position, byte[] bytes, int offset, int length) {
+    checkWrite(position, length);
+    try {
+      seekToOffset(position);
+      randomAccessFile.write(bytes, (int) offset, (int) length);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes writeByte(int offset, int b) {
+    checkWrite(offset, BYTE);
+    try {
+      seekToOffset(offset);
+      randomAccessFile.writeByte(b);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes writeChar(int offset, char c) {
+    checkWrite(offset, CHARACTER);
+    try {
+      seekToOffset(offset);
+      randomAccessFile.writeChar(c);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes writeShort(int offset, short s) {
+    checkWrite(offset, SHORT);
+    try {
+      seekToOffset(offset);
+      randomAccessFile.writeShort(s);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes writeInt(int offset, int i) {
+    checkWrite(offset, INTEGER);
+    try {
+      seekToOffset(offset);
+      randomAccessFile.writeInt(i);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes writeLong(int offset, long l) {
+    checkWrite(offset, LONG);
+    try {
+      seekToOffset(offset);
+      randomAccessFile.writeLong(l);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes writeFloat(int offset, float f) {
+    checkWrite(offset, FLOAT);
+    try {
+      seekToOffset(offset);
+      randomAccessFile.writeFloat(f);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes writeDouble(int offset, double d) {
+    checkWrite(offset, DOUBLE);
+    try {
+      seekToOffset(offset);
+      randomAccessFile.writeDouble(d);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public Bytes flush() {
+    try {
+      randomAccessFile.getFD().sync();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public void close() {
+    try {
+      randomAccessFile.close();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    super.close();
+  }
+
+  /**
+   * Deletes the underlying file.
+   */
+  public void delete() {
+    try {
+      close();
+      Files.delete(file.toPath());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/HeapBuffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/HeapBuffer.java
new file mode 100644 (file)
index 0000000..509f792
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.memory.Memory;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Direct {@link java.nio.ByteBuffer} based buffer.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class HeapBuffer extends ByteBufferBuffer {
+
+  /**
+   * Allocates a direct buffer with an initial capacity of {@code 4096} and a maximum capacity of {@link Long#MAX_VALUE}.
+   *
+   * @return The direct buffer.
+   * @see HeapBuffer#allocate(int)
+   * @see HeapBuffer#allocate(int, int)
+   */
+  public static HeapBuffer allocate() {
+    return allocate(DEFAULT_INITIAL_CAPACITY, MAX_SIZE);
+  }
+
+  /**
+   * Allocates a direct buffer with the given initial capacity.
+   *
+   * @param initialCapacity The initial capacity of the buffer to allocate (in bytes).
+   * @return The direct buffer.
+   * @throws IllegalArgumentException If {@code capacity} is greater than the maximum allowed count for
+   *                                  a {@link java.nio.ByteBuffer} - {@code Integer.MAX_VALUE - 5}
+   * @see HeapBuffer#allocate()
+   * @see HeapBuffer#allocate(int, int)
+   */
+  public static HeapBuffer allocate(int initialCapacity) {
+    return allocate(initialCapacity, MAX_SIZE);
+  }
+
+  /**
+   * Allocates a new direct buffer.
+   *
+   * @param initialCapacity The initial capacity of the buffer to allocate (in bytes).
+   * @param maxCapacity     The maximum capacity of the buffer.
+   * @return The direct buffer.
+   * @throws IllegalArgumentException If {@code capacity} or {@code maxCapacity} is greater than the maximum
+   *                                  allowed count for a {@link java.nio.ByteBuffer} - {@code Integer.MAX_VALUE - 5}
+   * @see HeapBuffer#allocate()
+   * @see HeapBuffer#allocate(int)
+   */
+  public static HeapBuffer allocate(int initialCapacity, int maxCapacity) {
+    checkArgument(initialCapacity <= maxCapacity, "initial capacity cannot be greater than maximum capacity");
+    return new HeapBuffer(HeapBytes.allocate((int) Math.min(Memory.Util.toPow2(initialCapacity), MAX_SIZE)), 0, initialCapacity, maxCapacity);
+  }
+
+  /**
+   * Wraps the given bytes in a heap buffer.
+   * <p>
+   * The buffer will be created with an initial capacity and maximum capacity equal to the byte array count.
+   *
+   * @param bytes The bytes to wrap.
+   * @return The wrapped bytes.
+   */
+  public static HeapBuffer wrap(byte[] bytes) {
+    return new HeapBuffer(HeapBytes.wrap(bytes), 0, bytes.length, bytes.length);
+  }
+
+  private final HeapBytes bytes;
+
+  protected HeapBuffer(HeapBytes bytes, int offset, int initialCapacity, int maxCapacity) {
+    super(bytes, offset, initialCapacity, maxCapacity, null);
+    this.bytes = bytes;
+  }
+
+  @Override
+  public boolean hasArray() {
+    return true;
+  }
+
+  @Override
+  public HeapBuffer duplicate() {
+    return new HeapBuffer(bytes, offset(), capacity(), maxCapacity());
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/HeapBytes.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/HeapBytes.java
new file mode 100644 (file)
index 0000000..7821c2e
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * {@link ByteBuffer} based heap bytes.
+ */
+public class HeapBytes extends ByteBufferBytes {
+  public static final byte[] EMPTY = new byte[0];
+
+  /**
+   * Allocates a new heap byte array.
+   *
+   * @param size The count of the buffer to allocate (in bytes).
+   * @return The heap buffer.
+   * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed count for
+   *                                  an array on the Java heap - {@code Integer.MAX_VALUE - 5}
+   */
+  public static HeapBytes allocate(int size) {
+    if (size > MAX_SIZE) {
+      throw new IllegalArgumentException("size cannot for HeapBytes cannot be greater than " + MAX_SIZE);
+    }
+    return new HeapBytes(ByteBuffer.allocate((int) size));
+  }
+
+  /**
+   * Wraps the given bytes in a {@link HeapBytes} object.
+   * <p>
+   * The returned {@link Bytes} object will be backed by a {@link ByteBuffer} instance that
+   * wraps the given byte array. The {@link Bytes#size()} will be equivalent to the provided
+   * by array {@code length}.
+   *
+   * @param bytes The bytes to wrap.
+   */
+  public static HeapBytes wrap(byte[] bytes) {
+    return new HeapBytes(ByteBuffer.wrap(bytes));
+  }
+
+  protected HeapBytes(ByteBuffer buffer) {
+    super(buffer);
+  }
+
+  @Override
+  protected ByteBuffer newByteBuffer(int size) {
+    return ByteBuffer.allocate((int) size);
+  }
+
+  @Override
+  public boolean hasArray() {
+    return true;
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/MappedBuffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/MappedBuffer.java
new file mode 100644 (file)
index 0000000..87731b4
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.io.File;
+import java.nio.channels.FileChannel;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Direct {@link java.nio.ByteBuffer} based buffer.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class MappedBuffer extends ByteBufferBuffer {
+
+  /**
+   * Allocates a dynamic capacity mapped buffer in {@link FileChannel.MapMode#READ_WRITE} mode with an initial capacity
+   * of {@code 16MiB} and a maximum capacity of {@link Integer#MAX_VALUE}.
+   * <p>
+   * The resulting buffer will have a maximum capacity of {@link Integer#MAX_VALUE}. As bytes are written to the buffer
+   * its capacity will double in count each time the current capacity is reached. Memory will be mapped by opening and
+   * expanding the given {@link File} to the desired {@code capacity} and mapping the file contents into memory via
+   * {@link FileChannel#map(FileChannel.MapMode, long, long)}.
+   *
+   * @param file The file to map into memory.
+   * @return The mapped buffer.
+   * @throws NullPointerException If {@code file} is {@code null}
+   * @see #allocate(File, FileChannel.MapMode)
+   * @see #allocate(File, int)
+   * @see #allocate(File, FileChannel.MapMode, int)
+   * @see #allocate(File, int, int)
+   * @see #allocate(File, FileChannel.MapMode, int, int)
+   */
+  public static MappedBuffer allocate(File file) {
+    return allocate(file, FileChannel.MapMode.READ_WRITE, DEFAULT_INITIAL_CAPACITY, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Allocates a dynamic capacity mapped buffer in {@link FileChannel.MapMode#READ_WRITE} mode with an initial capacity
+   * of {@code 16MiB} and a maximum capacity of {@link Integer#MAX_VALUE}.
+   * <p>
+   * The resulting buffer will be initialized to a capacity of {@code 4096} and have a maximum capacity of
+   * {@link Integer#MAX_VALUE}. As bytes are written to the buffer its capacity will double in count each time the current
+   * capacity is reached. Memory will be mapped by opening and expanding the given {@link File} to the desired
+   * {@code capacity} and mapping the file contents into memory via
+   * {@link FileChannel#map(FileChannel.MapMode, long, long)}.
+   *
+   * @param file The file to map into memory.
+   * @param mode The mode with which to map the file.
+   * @return The mapped buffer.
+   * @throws NullPointerException If {@code file} is {@code null}
+   * @see #allocate(File)
+   * @see #allocate(File, int)
+   * @see #allocate(File, FileChannel.MapMode, int)
+   * @see #allocate(File, int, int)
+   * @see #allocate(File, FileChannel.MapMode, int, int)
+   */
+  public static MappedBuffer allocate(File file, FileChannel.MapMode mode) {
+    return allocate(file, mode, DEFAULT_INITIAL_CAPACITY, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Allocates a fixed capacity mapped buffer in {@link FileChannel.MapMode#READ_WRITE} mode.
+   * <p>
+   * Memory will be mapped by opening and expanding the given {@link File} to the desired {@code capacity} and mapping the
+   * file contents into memory via {@link FileChannel#map(FileChannel.MapMode, long, long)}.
+   * <p>
+   * The resulting buffer will have a capacity of {@code capacity}. The underlying {@link MappedBytes} will be
+   * initialized to the next power of {@code 2}.
+   *
+   * @param file     The file to map into memory.
+   * @param capacity The fixed capacity of the buffer to allocate (in bytes).
+   * @return The mapped buffer.
+   * @throws NullPointerException     If {@code file} is {@code null}
+   * @throws IllegalArgumentException If the {@code capacity} is greater than {@link Integer#MAX_VALUE}.
+   * @see #allocate(File)
+   * @see #allocate(File, FileChannel.MapMode)
+   * @see #allocate(File, FileChannel.MapMode, int)
+   * @see #allocate(File, int, int)
+   * @see #allocate(File, FileChannel.MapMode, int, int)
+   */
+  public static MappedBuffer allocate(File file, int capacity) {
+    return allocate(file, FileChannel.MapMode.READ_WRITE, capacity, capacity);
+  }
+
+  /**
+   * Allocates a fixed capacity mapped buffer in the given {@link FileChannel.MapMode}.
+   * <p>
+   * Memory will be mapped by opening and expanding the given {@link File} to the desired {@code capacity} and mapping the
+   * file contents into memory via {@link FileChannel#map(FileChannel.MapMode, long, long)}.
+   * <p>
+   * The resulting buffer will have a capacity of {@code capacity}. The underlying {@link MappedBytes} will be
+   * initialized to the next power of {@code 2}.
+   *
+   * @param file     The file to map into memory.
+   * @param mode     The mode with which to map the file.
+   * @param capacity The fixed capacity of the buffer to allocate (in bytes).
+   * @return The mapped buffer.
+   * @throws NullPointerException     If {@code file} is {@code null}
+   * @throws IllegalArgumentException If the {@code capacity} is greater than {@link Integer#MAX_VALUE}.
+   * @see #allocate(File)
+   * @see #allocate(File, FileChannel.MapMode)
+   * @see #allocate(File, int)
+   * @see #allocate(File, int, int)
+   * @see #allocate(File, FileChannel.MapMode, int, int)
+   */
+  public static MappedBuffer allocate(File file, FileChannel.MapMode mode, int capacity) {
+    return allocate(file, mode, capacity, capacity);
+  }
+
+  /**
+   * Allocates a mapped buffer.
+   * <p>
+   * Memory will be mapped by opening and expanding the given {@link File} to the desired {@code count} and mapping the
+   * file contents into memory via {@link FileChannel#map(FileChannel.MapMode, long, long)}.
+   * <p>
+   * The resulting buffer will have a capacity of {@code initialCapacity}. The underlying {@link MappedBytes} will be
+   * initialized to the next power of {@code 2}. As bytes are written to the buffer, the buffer's capacity will double
+   * as int as {@code maxCapacity > capacity}.
+   *
+   * @param file            The file to map into memory. If the file doesn't exist it will be automatically created.
+   * @param initialCapacity The initial capacity of the buffer.
+   * @param maxCapacity     The maximum capacity of the buffer.
+   * @return The mapped buffer.
+   * @throws NullPointerException     If {@code file} is {@code null}
+   * @throws IllegalArgumentException If the {@code capacity} or {@code maxCapacity} is greater than
+   *                                  {@link Integer#MAX_VALUE}.
+   * @see #allocate(File)
+   * @see #allocate(File, FileChannel.MapMode)
+   * @see #allocate(File, int)
+   * @see #allocate(File, FileChannel.MapMode, int)
+   * @see #allocate(File, FileChannel.MapMode, int, int)
+   */
+  public static MappedBuffer allocate(File file, int initialCapacity, int maxCapacity) {
+    return allocate(file, FileChannel.MapMode.READ_WRITE, initialCapacity, maxCapacity);
+  }
+
+  /**
+   * Allocates a mapped buffer.
+   * <p>
+   * Memory will be mapped by opening and expanding the given {@link File} to the desired {@code count} and mapping the
+   * file contents into memory via {@link FileChannel#map(FileChannel.MapMode, long, long)}.
+   * <p>
+   * The resulting buffer will have a capacity of {@code initialCapacity}. The underlying {@link MappedBytes} will be
+   * initialized to the next power of {@code 2}. As bytes are written to the buffer, the buffer's capacity will double
+   * as int as {@code maxCapacity > capacity}.
+   *
+   * @param file            The file to map into memory. If the file doesn't exist it will be automatically created.
+   * @param mode            The mode with which to map the file.
+   * @param initialCapacity The initial capacity of the buffer.
+   * @param maxCapacity     The maximum capacity of the buffer.
+   * @return The mapped buffer.
+   * @throws NullPointerException     If {@code file} is {@code null}
+   * @throws IllegalArgumentException If the {@code capacity} or {@code maxCapacity} is greater than
+   *                                  {@link Integer#MAX_VALUE}.
+   * @see #allocate(File)
+   * @see #allocate(File, FileChannel.MapMode)
+   * @see #allocate(File, int)
+   * @see #allocate(File, FileChannel.MapMode, int)
+   * @see #allocate(File, int, int)
+   */
+  public static MappedBuffer allocate(File file, FileChannel.MapMode mode, int initialCapacity, int maxCapacity) {
+    checkNotNull(file, "file cannot be null");
+    checkNotNull(mode, "mode cannot be null");
+    checkArgument(initialCapacity <= maxCapacity, "initial capacity cannot be greater than maximum capacity");
+    return new MappedBuffer(MappedBytes.allocate(file, mode, initialCapacity), 0, initialCapacity, maxCapacity);
+  }
+
+  protected MappedBuffer(MappedBytes bytes, int offset, int initialCapacity, int maxCapacity) {
+    super(bytes, offset, initialCapacity, maxCapacity, null);
+  }
+
+  @Override
+  public MappedBuffer duplicate() {
+    return new MappedBuffer((MappedBytes) bytes, offset(), capacity(), maxCapacity());
+  }
+
+  /**
+   * Deletes the underlying file.
+   */
+  public void delete() {
+    ((MappedBytes) bytes).delete();
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/MappedBytes.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/MappedBytes.java
new file mode 100644 (file)
index 0000000..5ad4c03
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.AtomixIOException;
+import io.atomix.utils.memory.BufferCleaner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+
+/**
+ * {@link ByteBuffer} based mapped bytes.
+ */
+public class MappedBytes extends ByteBufferBytes {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(MappedBytes.class);
+
+  /**
+   * Allocates a mapped buffer in {@link FileChannel.MapMode#READ_WRITE} mode.
+   * <p>
+   * Memory will be mapped by opening and expanding the given {@link File} to the desired {@code count} and mapping the
+   * file contents into memory via {@link FileChannel#map(FileChannel.MapMode, long, long)}.
+   *
+   * @param file The file to map into memory. If the file doesn't exist it will be automatically created.
+   * @param size The count of the buffer to allocate (in bytes).
+   * @return The mapped buffer.
+   * @throws NullPointerException     If {@code file} is {@code null}
+   * @throws IllegalArgumentException If {@code count} is greater than {@link MappedBytes#MAX_SIZE}
+   * @see #allocate(File, FileChannel.MapMode, int)
+   */
+  public static MappedBytes allocate(File file, int size) {
+    return allocate(file, FileChannel.MapMode.READ_WRITE, size);
+  }
+
+  /**
+   * Allocates a mapped buffer.
+   * <p>
+   * Memory will be mapped by opening and expanding the given {@link File} to the desired {@code count} and mapping the
+   * file contents into memory via {@link FileChannel#map(FileChannel.MapMode, long, long)}.
+   *
+   * @param file The file to map into memory. If the file doesn't exist it will be automatically created.
+   * @param mode The mode with which to map the file.
+   * @param size The count of the buffer to allocate (in bytes).
+   * @return The mapped buffer.
+   * @throws NullPointerException     If {@code file} is {@code null}
+   * @throws IllegalArgumentException If {@code count} is greater than {@link Integer#MAX_VALUE}
+   * @see #allocate(File, int)
+   */
+  public static MappedBytes allocate(File file, FileChannel.MapMode mode, int size) {
+    return FileBytes.allocate(file, size).map(0, size, mode);
+  }
+
+  private final File file;
+  private final RandomAccessFile randomAccessFile;
+  private final FileChannel.MapMode mode;
+
+  protected MappedBytes(File file, RandomAccessFile randomAccessFile, MappedByteBuffer buffer, FileChannel.MapMode mode) {
+    super(buffer);
+    this.file = file;
+    this.randomAccessFile = randomAccessFile;
+    this.mode = mode;
+  }
+
+  @Override
+  protected ByteBuffer newByteBuffer(int size) {
+    try {
+      return randomAccessFile.getChannel().map(mode, 0, size);
+    } catch (IOException e) {
+      throw new AtomixIOException(e);
+    }
+  }
+
+  @Override
+  public boolean isDirect() {
+    return true;
+  }
+
+  @Override
+  public Bytes flush() {
+    ((MappedByteBuffer) buffer).force();
+    return this;
+  }
+
+  @Override
+  public void close() {
+    try {
+      BufferCleaner.freeBuffer(buffer);
+    } catch (Exception e) {
+      if (LOGGER.isDebugEnabled()) {
+        LOGGER.debug("Failed to unmap direct buffer", e);
+      }
+    }
+    try {
+      randomAccessFile.close();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    super.close();
+  }
+
+  /**
+   * Deletes the underlying file.
+   */
+  public void delete() {
+    try {
+      close();
+      Files.delete(file.toPath());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static String parseMode(FileChannel.MapMode mode) {
+    if (mode == FileChannel.MapMode.READ_ONLY) {
+      return "r";
+    } else if (mode == FileChannel.MapMode.READ_WRITE) {
+      return "rw";
+    }
+    throw new IllegalArgumentException("unsupported map mode");
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/PooledAllocator.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/PooledAllocator.java
new file mode 100644 (file)
index 0000000..8dbe978
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.concurrent.ReferencePool;
+
+/**
+ * Pooled buffer allocator.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public abstract class PooledAllocator implements BufferAllocator {
+  private final ReferencePool<AbstractBuffer> pool;
+
+  protected PooledAllocator(ReferencePool<AbstractBuffer> pool) {
+    this.pool = pool;
+  }
+
+  /**
+   * Returns the maximum buffer capacity.
+   *
+   * @return The maximum buffer capacity.
+   */
+  protected abstract int maxCapacity();
+
+  @Override
+  public Buffer allocate() {
+    return allocate(4096, maxCapacity());
+  }
+
+  @Override
+  public Buffer allocate(int capacity) {
+    return allocate(capacity, maxCapacity());
+  }
+
+  @Override
+  public Buffer allocate(int initialCapacity, int maxCapacity) {
+    return pool.acquire().reset(0, initialCapacity, maxCapacity).clear();
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/ReadOnlyBuffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/ReadOnlyBuffer.java
new file mode 100644 (file)
index 0000000..5378b68
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.concurrent.ReferenceManager;
+
+import java.nio.ReadOnlyBufferException;
+
+/**
+ * Read-only buffer.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class ReadOnlyBuffer extends AbstractBuffer {
+  private final Buffer root;
+
+  public ReadOnlyBuffer(Buffer buffer, ReferenceManager<Buffer> referenceManager) {
+    super(buffer.bytes(), referenceManager);
+    this.root = buffer;
+  }
+
+  @Override
+  public boolean isDirect() {
+    return root.isDirect();
+  }
+
+  @Override
+  public boolean isFile() {
+    return root.isFile();
+  }
+
+  @Override
+  public boolean isReadOnly() {
+    return true;
+  }
+
+  @Override
+  public Buffer compact() {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  protected void compact(int from, int to, int length) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer duplicate() {
+    return new ReadOnlyBuffer(root, referenceManager);
+  }
+
+  @Override
+  public Buffer acquire() {
+    root.acquire();
+    return this;
+  }
+
+  @Override
+  public boolean release() {
+    return root.release();
+  }
+
+  @Override
+  public Buffer zero(int offset, int length) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer zero(int offset) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer zero() {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeBoolean(int offset, boolean b) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer write(Buffer buffer) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer write(Bytes bytes) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer write(Bytes bytes, int offset, int length) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer write(int offset, Bytes bytes, int srcOffset, int length) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer write(byte[] bytes) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer write(byte[] bytes, int offset, int length) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer write(int offset, byte[] bytes, int srcOffset, int length) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeByte(int b) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeByte(int offset, int b) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeUnsignedByte(int b) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeUnsignedByte(int offset, int b) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeChar(char c) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeChar(int offset, char c) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeShort(short s) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeShort(int offset, short s) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeUnsignedShort(int s) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeUnsignedShort(int offset, int s) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeMedium(int m) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeMedium(int offset, int m) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeUnsignedMedium(int m) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeUnsignedMedium(int offset, int m) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeInt(int i) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeInt(int offset, int i) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeUnsignedInt(long i) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeUnsignedInt(int offset, long i) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeLong(long l) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeLong(int offset, long l) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeFloat(float f) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeFloat(int offset, float f) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeDouble(double d) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeDouble(int offset, double d) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeBoolean(boolean b) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer writeUTF8(String s) {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public Buffer flush() {
+    throw new ReadOnlyBufferException();
+  }
+
+  @Override
+  public void close() {
+    root.release();
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/SlicedBuffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/SlicedBuffer.java
new file mode 100644 (file)
index 0000000..fe34f25
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+/**
+ * Sliced buffer.
+ * <p>
+ * The sliced buffer provides a view of a subset of an underlying buffer. This buffer operates directly on the {@link Bytes}
+ * underlying the child {@link Buffer} instance.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class SlicedBuffer extends AbstractBuffer {
+  private final Buffer root;
+
+  public SlicedBuffer(Buffer root, Bytes bytes, int offset, int initialCapacity, int maxCapacity) {
+    super(bytes, offset, initialCapacity, maxCapacity, null);
+    this.root = root;
+    root.acquire();
+  }
+
+  /**
+   * Returns the root buffer.
+   *
+   * @return The root buffer.
+   */
+  public Buffer root() {
+    return root;
+  }
+
+  @Override
+  public boolean isDirect() {
+    return root.isDirect();
+  }
+
+  @Override
+  protected void compact(int from, int to, int length) {
+    if (root instanceof AbstractBuffer) {
+      ((AbstractBuffer) root).compact(from, to, length);
+    }
+  }
+
+  @Override
+  public boolean isFile() {
+    return root.isFile();
+  }
+
+  @Override
+  public boolean isReadOnly() {
+    return root.isReadOnly();
+  }
+
+  @Override
+  public Buffer compact() {
+    return null;
+  }
+
+  @Override
+  public Buffer duplicate() {
+    return new SlicedBuffer(root, bytes, offset(), capacity(), maxCapacity());
+  }
+
+  @Override
+  public Buffer acquire() {
+    root.acquire();
+    return this;
+  }
+
+  @Override
+  public boolean release() {
+    return root.release();
+  }
+
+  @Override
+  public void close() {
+    root.release();
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/SwappedBuffer.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/SwappedBuffer.java
new file mode 100644 (file)
index 0000000..3b30a61
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import io.atomix.utils.concurrent.ReferenceManager;
+
+import java.nio.ByteOrder;
+
+/**
+ * Byte order swapped buffer.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class SwappedBuffer extends AbstractBuffer {
+  private final Buffer root;
+
+  SwappedBuffer(Buffer root, Bytes bytes, ReferenceManager<Buffer> referenceManager) {
+    super(bytes, referenceManager);
+    this.root = root;
+  }
+
+  public SwappedBuffer(Buffer buffer, int offset, int initialCapacity, int maxCapacity, ReferenceManager<Buffer> referenceManager) {
+    super(buffer.bytes().order(buffer.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN), offset, initialCapacity, maxCapacity, referenceManager);
+    this.root = buffer instanceof SwappedBuffer ? ((SwappedBuffer) buffer).root : buffer;
+    root.acquire();
+  }
+
+  /**
+   * Returns the root buffer.
+   *
+   * @return The root buffer.
+   */
+  public Buffer root() {
+    return root;
+  }
+
+  @Override
+  public boolean isDirect() {
+    return root.isDirect();
+  }
+
+  @Override
+  public boolean isFile() {
+    return root.isFile();
+  }
+
+  @Override
+  public boolean isReadOnly() {
+    return root.isReadOnly();
+  }
+
+  @Override
+  protected void compact(int from, int to, int length) {
+    if (root instanceof AbstractBuffer) {
+      ((AbstractBuffer) root).compact(from, to, length);
+    }
+  }
+
+  @Override
+  public Buffer duplicate() {
+    return new SwappedBuffer(root, offset(), capacity(), maxCapacity(), referenceManager);
+  }
+
+  @Override
+  public Buffer acquire() {
+    root.acquire();
+    return this;
+  }
+
+  @Override
+  public boolean release() {
+    return root.release();
+  }
+
+  @Override
+  public void close() {
+    root.release();
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/SwappedBytes.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/SwappedBytes.java
new file mode 100644 (file)
index 0000000..2bcda70
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.ByteOrder;
+
+/**
+ * Bytes in swapped order.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class SwappedBytes extends WrappedBytes {
+
+  public SwappedBytes(Bytes bytes) {
+    super(bytes);
+  }
+
+  @Override
+  public ByteOrder order() {
+    return bytes.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
+  }
+
+  @Override
+  public char readChar(int offset) {
+    return Character.reverseBytes(bytes.readChar(offset));
+  }
+
+  @Override
+  public short readShort(int offset) {
+    return Short.reverseBytes(bytes.readShort(offset));
+  }
+
+  @Override
+  public int readUnsignedShort(int offset) {
+    return Short.reverseBytes(bytes.readShort(offset)) & 0xFFFF;
+  }
+
+  @Override
+  public int readMedium(int offset) {
+    return Integer.reverseBytes(bytes.readMedium(offset));
+  }
+
+  @Override
+  public int readUnsignedMedium(int offset) {
+    return Integer.reverseBytes(bytes.readUnsignedMedium(offset));
+  }
+
+  @Override
+  public int readInt(int offset) {
+    return Integer.reverseBytes(bytes.readInt(offset));
+  }
+
+  @Override
+  public long readUnsignedInt(int offset) {
+    return Integer.reverseBytes(bytes.readInt(offset)) & 0xFFFFFFFFL;
+  }
+
+  @Override
+  public long readLong(int offset) {
+    return Long.reverseBytes(bytes.readLong(offset));
+  }
+
+  @Override
+  public float readFloat(int offset) {
+    return Float.intBitsToFloat(readInt(offset));
+  }
+
+  @Override
+  public double readDouble(int offset) {
+    return Double.longBitsToDouble(readLong(offset));
+  }
+
+  @Override
+  public Bytes writeChar(int offset, char c) {
+    bytes.writeChar(offset, Character.reverseBytes(c));
+    return this;
+  }
+
+  @Override
+  public Bytes writeShort(int offset, short s) {
+    bytes.writeShort(offset, Short.reverseBytes(s));
+    return this;
+  }
+
+  @Override
+  public Bytes writeUnsignedShort(int offset, int s) {
+    bytes.writeUnsignedShort(offset, Short.reverseBytes((short) s));
+    return this;
+  }
+
+  @Override
+  public Bytes writeMedium(int offset, int m) {
+    bytes.writeMedium(offset, Integer.reverseBytes(m));
+    return this;
+  }
+
+  @Override
+  public Bytes writeUnsignedMedium(int offset, int m) {
+    bytes.writeUnsignedMedium(offset, Integer.reverseBytes(m));
+    return this;
+  }
+
+  @Override
+  public Bytes writeInt(int offset, int i) {
+    bytes.writeInt(offset, Integer.reverseBytes(i));
+    return this;
+  }
+
+  @Override
+  public Bytes writeUnsignedInt(int offset, long i) {
+    bytes.writeUnsignedInt(offset, Integer.reverseBytes((int) i));
+    return this;
+  }
+
+  @Override
+  public Bytes writeLong(int offset, long l) {
+    bytes.writeLong(offset, Long.reverseBytes(l));
+    return this;
+  }
+
+  @Override
+  public Bytes writeFloat(int offset, float f) {
+    return writeInt(offset, Float.floatToRawIntBits(f));
+  }
+
+  @Override
+  public Bytes writeDouble(int offset, double d) {
+    return writeLong(offset, Double.doubleToRawLongBits(d));
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/UnpooledAllocator.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/UnpooledAllocator.java
new file mode 100644 (file)
index 0000000..6a2ccab
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+/**
+ * Unpooled buffer allocator.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public abstract class UnpooledAllocator implements BufferAllocator {
+
+  /**
+   * Returns the maximum buffer capacity.
+   *
+   * @return The maximum buffer capacity.
+   */
+  protected abstract int maxCapacity();
+
+  @Override
+  public Buffer allocate() {
+    return allocate(4096, maxCapacity());
+  }
+
+  @Override
+  public Buffer allocate(int capacity) {
+    return allocate(capacity, capacity);
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/UnpooledDirectAllocator.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/UnpooledDirectAllocator.java
new file mode 100644 (file)
index 0000000..ada468f
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+/**
+ * Unpooled direct allocator.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class UnpooledDirectAllocator extends UnpooledAllocator {
+
+  @Override
+  public Buffer allocate(int initialCapacity, int maxCapacity) {
+    return DirectBuffer.allocate(initialCapacity, maxCapacity);
+  }
+
+  @Override
+  protected int maxCapacity() {
+    return Integer.MAX_VALUE;
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/UnpooledHeapAllocator.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/UnpooledHeapAllocator.java
new file mode 100644 (file)
index 0000000..fe90415
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+/**
+ * Unpooled heap allocator.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class UnpooledHeapAllocator extends UnpooledAllocator {
+
+  @Override
+  protected int maxCapacity() {
+    return HeapBuffer.MAX_SIZE;
+  }
+
+  @Override
+  public Buffer allocate(int initialCapacity, int maxCapacity) {
+    return HeapBuffer.allocate(initialCapacity, maxCapacity);
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/WrappedBytes.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/WrappedBytes.java
new file mode 100644 (file)
index 0000000..474f0d3
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.nio.ByteOrder;
+
+/**
+ * Wrapped bytes.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class WrappedBytes extends AbstractBytes {
+  protected final Bytes bytes;
+  private final Bytes root;
+
+  public WrappedBytes(Bytes bytes) {
+    if (bytes == null) {
+      throw new NullPointerException("bytes cannot be null");
+    }
+    this.bytes = bytes;
+    this.root = bytes instanceof WrappedBytes ? ((WrappedBytes) bytes).root : bytes;
+  }
+
+  /**
+   * Returns the root bytes.
+   */
+  public Bytes root() {
+    return root;
+  }
+
+  @Override
+  public int size() {
+    return bytes.size();
+  }
+
+  @Override
+  public Bytes resize(int newSize) {
+    return bytes.resize(newSize);
+  }
+
+  @Override
+  public ByteOrder order() {
+    return bytes.order();
+  }
+
+  @Override
+  public Bytes zero() {
+    bytes.zero();
+    return this;
+  }
+
+  @Override
+  public Bytes zero(int offset) {
+    bytes.zero(offset);
+    return this;
+  }
+
+  @Override
+  public Bytes zero(int offset, int length) {
+    bytes.zero(offset, length);
+    return this;
+  }
+
+  @Override
+  public Bytes read(int offset, Bytes dst, int dstOffset, int length) {
+    bytes.read(offset, dst, dstOffset, length);
+    return this;
+  }
+
+  @Override
+  public Bytes read(int offset, byte[] dst, int dstOffset, int length) {
+    bytes.read(offset, dst, dstOffset, length);
+    return this;
+  }
+
+  @Override
+  public int readByte(int offset) {
+    return bytes.readByte(offset);
+  }
+
+  @Override
+  public int readUnsignedByte(int offset) {
+    return bytes.readUnsignedByte(offset);
+  }
+
+  @Override
+  public char readChar(int offset) {
+    return bytes.readChar(offset);
+  }
+
+  @Override
+  public short readShort(int offset) {
+    return bytes.readShort(offset);
+  }
+
+  @Override
+  public int readUnsignedShort(int offset) {
+    return bytes.readUnsignedShort(offset);
+  }
+
+  @Override
+  public int readMedium(int offset) {
+    return bytes.readMedium(offset);
+  }
+
+  @Override
+  public int readUnsignedMedium(int offset) {
+    return bytes.readUnsignedMedium(offset);
+  }
+
+  @Override
+  public int readInt(int offset) {
+    return bytes.readInt(offset);
+  }
+
+  @Override
+  public long readUnsignedInt(int offset) {
+    return bytes.readUnsignedInt(offset);
+  }
+
+  @Override
+  public long readLong(int offset) {
+    return bytes.readLong(offset);
+  }
+
+  @Override
+  public float readFloat(int offset) {
+    return bytes.readFloat(offset);
+  }
+
+  @Override
+  public double readDouble(int offset) {
+    return bytes.readDouble(offset);
+  }
+
+  @Override
+  public boolean readBoolean(int offset) {
+    return bytes.readBoolean(offset);
+  }
+
+  @Override
+  public String readString(int offset) {
+    return bytes.readString(offset);
+  }
+
+  @Override
+  public String readUTF8(int offset) {
+    return bytes.readUTF8(offset);
+  }
+
+  @Override
+  public Bytes write(int offset, Bytes src, int srcOffset, int length) {
+    bytes.write(offset, src, srcOffset, length);
+    return this;
+  }
+
+  @Override
+  public Bytes write(int offset, byte[] src, int srcOffset, int length) {
+    bytes.write(offset, src, srcOffset, length);
+    return this;
+  }
+
+  @Override
+  public Bytes writeByte(int offset, int b) {
+    bytes.writeByte(offset, b);
+    return this;
+  }
+
+  @Override
+  public Bytes writeUnsignedByte(int offset, int b) {
+    bytes.writeUnsignedByte(offset, b);
+    return this;
+  }
+
+  @Override
+  public Bytes writeChar(int offset, char c) {
+    bytes.writeChar(offset, c);
+    return this;
+  }
+
+  @Override
+  public Bytes writeShort(int offset, short s) {
+    bytes.writeShort(offset, s);
+    return this;
+  }
+
+  @Override
+  public Bytes writeUnsignedShort(int offset, int s) {
+    bytes.writeUnsignedShort(offset, s);
+    return this;
+  }
+
+  @Override
+  public Bytes writeMedium(int offset, int m) {
+    bytes.writeMedium(offset, m);
+    return this;
+  }
+
+  @Override
+  public Bytes writeUnsignedMedium(int offset, int m) {
+    bytes.writeUnsignedMedium(offset, m);
+    return this;
+  }
+
+  @Override
+  public Bytes writeInt(int offset, int i) {
+    bytes.writeInt(offset, i);
+    return this;
+  }
+
+  @Override
+  public Bytes writeUnsignedInt(int offset, long i) {
+    bytes.writeUnsignedInt(offset, i);
+    return this;
+  }
+
+  @Override
+  public Bytes writeLong(int offset, long l) {
+    bytes.writeLong(offset, l);
+    return this;
+  }
+
+  @Override
+  public Bytes writeFloat(int offset, float f) {
+    bytes.writeFloat(offset, f);
+    return this;
+  }
+
+  @Override
+  public Bytes writeDouble(int offset, double d) {
+    bytes.writeDouble(offset, d);
+    return this;
+  }
+
+  @Override
+  public Bytes writeBoolean(int offset, boolean b) {
+    bytes.writeBoolean(offset, b);
+    return this;
+  }
+
+  @Override
+  public Bytes writeString(int offset, String s) {
+    bytes.writeString(offset, s);
+    return this;
+  }
+
+  @Override
+  public Bytes writeUTF8(int offset, String s) {
+    bytes.writeUTF8(offset, s);
+    return this;
+  }
+
+  @Override
+  public Bytes flush() {
+    bytes.flush();
+    return this;
+  }
+
+  @Override
+  public void close() {
+    bytes.close();
+  }
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/package-info.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/buffer/package-info.java
new file mode 100644 (file)
index 0000000..df12607
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides a low-level {@link io.atomix.storage.buffer.Buffer} abstraction backed by on- or off-heap memory, memory mapped
+ * files, or {@link java.io.RandomAccessFile}.
+ */
+package io.atomix.storage.buffer;
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/DelegatingJournal.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/DelegatingJournal.java
new file mode 100644 (file)
index 0000000..de0e35f
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Delegating journal.
+ */
+public class DelegatingJournal<E> implements Journal<E> {
+  private final Journal<E> delegate;
+
+  public DelegatingJournal(Journal<E> delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public JournalWriter<E> writer() {
+    return delegate.writer();
+  }
+
+  @Override
+  public JournalReader<E> openReader(long index) {
+    return delegate.openReader(index);
+  }
+
+  @Override
+  public JournalReader<E> openReader(long index, JournalReader.Mode mode) {
+    return delegate.openReader(index, mode);
+  }
+
+  @Override
+  public boolean isOpen() {
+    return delegate.isOpen();
+  }
+
+  @Override
+  public void close() {
+    delegate.close();
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("delegate", delegate)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/DelegatingJournalReader.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/DelegatingJournalReader.java
new file mode 100644 (file)
index 0000000..1d6af29
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Journal reader delegate.
+ */
+public class DelegatingJournalReader<E> implements JournalReader<E> {
+  private final JournalReader<E> delegate;
+
+  public DelegatingJournalReader(JournalReader<E> delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public long getFirstIndex() {
+    return delegate.getFirstIndex();
+  }
+
+  @Override
+  public long getCurrentIndex() {
+    return delegate.getCurrentIndex();
+  }
+
+  @Override
+  public Indexed<E> getCurrentEntry() {
+    return delegate.getCurrentEntry();
+  }
+
+  @Override
+  public long getNextIndex() {
+    return delegate.getNextIndex();
+  }
+
+  @Override
+  public boolean hasNext() {
+    return delegate.hasNext();
+  }
+
+  @Override
+  public Indexed<E> next() {
+    return delegate.next();
+  }
+
+  @Override
+  public void reset() {
+    delegate.reset();
+  }
+
+  @Override
+  public void reset(long index) {
+    delegate.reset(index);
+  }
+
+  @Override
+  public void close() {
+    delegate.close();
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("delegate", delegate)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/DelegatingJournalWriter.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/DelegatingJournalWriter.java
new file mode 100644 (file)
index 0000000..3285989
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Journal writer delegate.
+ */
+public class DelegatingJournalWriter<E> implements JournalWriter<E> {
+  private final JournalWriter<E> delegate;
+
+  public DelegatingJournalWriter(JournalWriter<E> delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public long getLastIndex() {
+    return delegate.getLastIndex();
+  }
+
+  @Override
+  public Indexed<E> getLastEntry() {
+    return delegate.getLastEntry();
+  }
+
+  @Override
+  public long getNextIndex() {
+    return delegate.getNextIndex();
+  }
+
+  @Override
+  public <T extends E> Indexed<T> append(T entry) {
+    return delegate.append(entry);
+  }
+
+  @Override
+  public void append(Indexed<E> entry) {
+    delegate.append(entry);
+  }
+
+  @Override
+  public void commit(long index) {
+    delegate.commit(index);
+  }
+
+  @Override
+  public void reset(long index) {
+    delegate.reset(index);
+  }
+
+  @Override
+  public void truncate(long index) {
+    delegate.truncate(index);
+  }
+
+  @Override
+  public void flush() {
+    delegate.flush();
+  }
+
+  @Override
+  public void close() {
+    delegate.close();
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("delegate", delegate)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/FileChannelJournalSegmentReader.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/FileChannelJournalSegmentReader.java
new file mode 100644 (file)
index 0000000..fe69711
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import io.atomix.storage.StorageException;
+import io.atomix.storage.journal.index.JournalIndex;
+import io.atomix.storage.journal.index.Position;
+import io.atomix.utils.serializer.Namespace;
+
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.NoSuchElementException;
+import java.util.zip.CRC32;
+import java.util.zip.Checksum;
+
+/**
+ * Log segment reader.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+class FileChannelJournalSegmentReader<E> implements JournalReader<E> {
+  private final FileChannel channel;
+  private final int maxEntrySize;
+  private final JournalIndex index;
+  private final Namespace namespace;
+  private final ByteBuffer memory;
+  private final long firstIndex;
+  private Indexed<E> currentEntry;
+  private Indexed<E> nextEntry;
+
+  FileChannelJournalSegmentReader(
+      FileChannel channel,
+      JournalSegment<E> segment,
+      int maxEntrySize,
+      JournalIndex index,
+      Namespace namespace) {
+    this.channel = channel;
+    this.maxEntrySize = maxEntrySize;
+    this.index = index;
+    this.namespace = namespace;
+    this.memory = ByteBuffer.allocate((maxEntrySize + Integer.BYTES + Integer.BYTES) * 2);
+    this.firstIndex = segment.index();
+    reset();
+  }
+
+  @Override
+  public long getFirstIndex() {
+    return firstIndex;
+  }
+
+  @Override
+  public long getCurrentIndex() {
+    return currentEntry != null ? currentEntry.index() : 0;
+  }
+
+  @Override
+  public Indexed<E> getCurrentEntry() {
+    return currentEntry;
+  }
+
+  @Override
+  public long getNextIndex() {
+    return currentEntry != null ? currentEntry.index() + 1 : firstIndex;
+  }
+
+  @Override
+  public void reset(long index) {
+    reset();
+    Position position = this.index.lookup(index - 1);
+    if (position != null) {
+      currentEntry = new Indexed<>(position.index() - 1, null, 0);
+      try {
+        channel.position(position.position());
+        memory.clear().flip();
+      } catch (IOException e) {
+        throw new StorageException(e);
+      }
+      readNext();
+    }
+    while (getNextIndex() < index && hasNext()) {
+      next();
+    }
+  }
+
+  @Override
+  public void reset() {
+    try {
+      channel.position(JournalSegmentDescriptor.BYTES);
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+    memory.clear().limit(0);
+    currentEntry = null;
+    nextEntry = null;
+    readNext();
+  }
+
+  @Override
+  public boolean hasNext() {
+    // If the next entry is null, check whether a next entry exists.
+    if (nextEntry == null) {
+      readNext();
+    }
+    return nextEntry != null;
+  }
+
+  @Override
+  public Indexed<E> next() {
+    if (!hasNext()) {
+      throw new NoSuchElementException();
+    }
+
+    // Set the current entry to the next entry.
+    currentEntry = nextEntry;
+
+    // Reset the next entry to null.
+    nextEntry = null;
+
+    // Read the next entry in the segment.
+    readNext();
+
+    // Return the current entry.
+    return currentEntry;
+  }
+
+  /**
+   * Reads the next entry in the segment.
+   */
+  @SuppressWarnings("unchecked")
+  private void readNext() {
+    // Compute the index of the next entry in the segment.
+    final long index = getNextIndex();
+
+    try {
+      // Read more bytes from the segment if necessary.
+      if (memory.remaining() < maxEntrySize) {
+        long position = channel.position() + memory.position();
+        channel.position(position);
+        memory.clear();
+        channel.read(memory);
+        channel.position(position);
+        memory.flip();
+      }
+
+      // Mark the buffer so it can be reset if necessary.
+      memory.mark();
+
+      try {
+        // Read the length of the entry.
+        final int length = memory.getInt();
+
+        // If the buffer length is zero then return.
+        if (length <= 0 || length > maxEntrySize) {
+          memory.reset().limit(memory.position());
+          nextEntry = null;
+          return;
+        }
+
+        // Read the checksum of the entry.
+        long checksum = memory.getInt() & 0xFFFFFFFFL;
+
+        // Compute the checksum for the entry bytes.
+        final Checksum crc32 = new CRC32();
+        crc32.update(memory.array(), memory.position(), length);
+
+        // If the stored checksum equals the computed checksum, return the entry.
+        if (checksum == crc32.getValue()) {
+          int limit = memory.limit();
+          memory.limit(memory.position() + length);
+          E entry = namespace.deserialize(memory);
+          memory.limit(limit);
+          nextEntry = new Indexed<>(index, entry, length);
+        } else {
+          memory.reset().limit(memory.position());
+          nextEntry = null;
+        }
+      } catch (BufferUnderflowException e) {
+        memory.reset().limit(memory.position());
+        nextEntry = null;
+      }
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  @Override
+  public void close() {
+    // Do nothing. The parent reader manages the channel.
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/FileChannelJournalSegmentWriter.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/FileChannelJournalSegmentWriter.java
new file mode 100644 (file)
index 0000000..28a0547
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import com.esotericsoftware.kryo.KryoException;
+import io.atomix.storage.StorageException;
+import io.atomix.storage.journal.index.JournalIndex;
+import io.atomix.utils.serializer.Namespace;
+
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.zip.CRC32;
+import java.util.zip.Checksum;
+
+/**
+ * Segment writer.
+ * <p>
+ * The format of an entry in the log is as follows:
+ * <ul>
+ * <li>64-bit index</li>
+ * <li>8-bit boolean indicating whether a term change is contained in the entry</li>
+ * <li>64-bit optional term</li>
+ * <li>32-bit signed entry length, including the entry type ID</li>
+ * <li>8-bit signed entry type ID</li>
+ * <li>n-bit entry bytes</li>
+ * </ul>
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+class FileChannelJournalSegmentWriter<E> implements JournalWriter<E> {
+  private final FileChannel channel;
+  private final JournalSegment segment;
+  private final int maxEntrySize;
+  private final JournalIndex index;
+  private final Namespace namespace;
+  private final ByteBuffer memory;
+  private final long firstIndex;
+  private Indexed<E> lastEntry;
+
+  FileChannelJournalSegmentWriter(
+      FileChannel channel,
+      JournalSegment segment,
+      int maxEntrySize,
+      JournalIndex index,
+      Namespace namespace) {
+    this.channel = channel;
+    this.segment = segment;
+    this.maxEntrySize = maxEntrySize;
+    this.index = index;
+    this.memory = ByteBuffer.allocate((maxEntrySize + Integer.BYTES + Integer.BYTES) * 2);
+    memory.limit(0);
+    this.namespace = namespace;
+    this.firstIndex = segment.index();
+    reset(0);
+  }
+
+  @Override
+  public void reset(long index) {
+    long nextIndex = firstIndex;
+
+    // Clear the buffer indexes.
+    try {
+      channel.position(JournalSegmentDescriptor.BYTES);
+      memory.clear().flip();
+
+      // Record the current buffer position.
+      long position = channel.position();
+
+      // Read more bytes from the segment if necessary.
+      if (memory.remaining() < maxEntrySize) {
+        memory.clear();
+        channel.read(memory);
+        channel.position(position);
+        memory.flip();
+      }
+
+      // Read the entry length.
+      memory.mark();
+      int length = memory.getInt();
+
+      // If the length is non-zero, read the entry.
+      while (0 < length && length <= maxEntrySize && (index == 0 || nextIndex <= index)) {
+
+        // Read the checksum of the entry.
+        final long checksum = memory.getInt() & 0xFFFFFFFFL;
+
+        // Compute the checksum for the entry bytes.
+        final Checksum crc32 = new CRC32();
+        crc32.update(memory.array(), memory.position(), length);
+
+        // If the stored checksum equals the computed checksum, return the entry.
+        if (checksum == crc32.getValue()) {
+          int limit = memory.limit();
+          memory.limit(memory.position() + length);
+          final E entry = namespace.deserialize(memory);
+          memory.limit(limit);
+          lastEntry = new Indexed<>(nextIndex, entry, length);
+          this.index.index(nextIndex, (int) position);
+          nextIndex++;
+        } else {
+          break;
+        }
+
+        // Update the current position for indexing.
+        position = channel.position() + memory.position();
+
+        // Read more bytes from the segment if necessary.
+        if (memory.remaining() < maxEntrySize) {
+          channel.position(position);
+          memory.clear();
+          channel.read(memory);
+          channel.position(position);
+          memory.flip();
+        }
+
+        memory.mark();
+        length = memory.getInt();
+      }
+
+      // Reset the buffer to the previous mark.
+      channel.position(channel.position() + memory.reset().position());
+    } catch (BufferUnderflowException e) {
+      try {
+        channel.position(channel.position() + memory.reset().position());
+      } catch (IOException e2) {
+        throw new StorageException(e2);
+      }
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  @Override
+  public long getLastIndex() {
+    return lastEntry != null ? lastEntry.index() : segment.index() - 1;
+  }
+
+  @Override
+  public Indexed<E> getLastEntry() {
+    return lastEntry;
+  }
+
+  @Override
+  public long getNextIndex() {
+    if (lastEntry != null) {
+      return lastEntry.index() + 1;
+    } else {
+      return firstIndex;
+    }
+  }
+
+  /**
+   * Returns the size of the underlying buffer.
+   *
+   * @return The size of the underlying buffer.
+   */
+  public long size() {
+    try {
+      return channel.position();
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  /**
+   * Returns a boolean indicating whether the segment is empty.
+   *
+   * @return Indicates whether the segment is empty.
+   */
+  public boolean isEmpty() {
+    return lastEntry == null;
+  }
+
+  /**
+   * Returns a boolean indicating whether the segment is full.
+   *
+   * @return Indicates whether the segment is full.
+   */
+  public boolean isFull() {
+    return size() >= segment.descriptor().maxSegmentSize()
+        || getNextIndex() - firstIndex >= segment.descriptor().maxEntries();
+  }
+
+  /**
+   * Returns the first index written to the segment.
+   */
+  public long firstIndex() {
+    return firstIndex;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public void append(Indexed<E> entry) {
+    final long nextIndex = getNextIndex();
+
+    // If the entry's index is greater than the next index in the segment, skip some entries.
+    if (entry.index() > nextIndex) {
+      throw new IndexOutOfBoundsException("Entry index is not sequential");
+    }
+
+    // If the entry's index is less than the next index, truncate the segment.
+    if (entry.index() < nextIndex) {
+      truncate(entry.index() - 1);
+    }
+    append(entry.entry());
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T extends E> Indexed<T> append(T entry) {
+    // Store the entry index.
+    final long index = getNextIndex();
+
+    try {
+      // Serialize the entry.
+      memory.clear();
+      memory.position(Integer.BYTES + Integer.BYTES);
+      try {
+        namespace.serialize(entry, memory);
+      } catch (KryoException e) {
+        throw new StorageException.TooLarge("Entry size exceeds maximum allowed bytes (" + maxEntrySize + ")");
+      }
+      memory.flip();
+
+      final int length = memory.limit() - (Integer.BYTES + Integer.BYTES);
+
+      // Ensure there's enough space left in the buffer to store the entry.
+      long position = channel.position();
+      if (segment.descriptor().maxSegmentSize() - position < length + Integer.BYTES + Integer.BYTES) {
+        throw new BufferOverflowException();
+      }
+
+      // If the entry length exceeds the maximum entry size then throw an exception.
+      if (length > maxEntrySize) {
+        throw new StorageException.TooLarge("Entry size " + length + " exceeds maximum allowed bytes (" + maxEntrySize + ")");
+      }
+
+      // Compute the checksum for the entry.
+      final Checksum crc32 = new CRC32();
+      crc32.update(memory.array(), Integer.BYTES + Integer.BYTES, memory.limit() - (Integer.BYTES + Integer.BYTES));
+      final long checksum = crc32.getValue();
+
+      // Create a single byte[] in memory for the entire entry and write it as a batch to the underlying buffer.
+      memory.putInt(0, length);
+      memory.putInt(Integer.BYTES, (int) checksum);
+      channel.write(memory);
+
+      // Update the last entry with the correct index/term/length.
+      Indexed<E> indexedEntry = new Indexed<>(index, entry, length);
+      this.lastEntry = indexedEntry;
+      this.index.index(index, (int) position);
+      return (Indexed<T>) indexedEntry;
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  @Override
+  public void commit(long index) {
+
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public void truncate(long index) {
+    // If the index is greater than or equal to the last index, skip the truncate.
+    if (index >= getLastIndex()) {
+      return;
+    }
+
+    // Reset the last entry.
+    lastEntry = null;
+
+    try {
+      // Truncate the index.
+      this.index.truncate(index);
+
+      if (index < segment.index()) {
+        channel.position(JournalSegmentDescriptor.BYTES);
+        channel.write(zero());
+        channel.position(JournalSegmentDescriptor.BYTES);
+      } else {
+        // Reset the writer to the given index.
+        reset(index);
+
+        // Zero entries after the given index.
+        long position = channel.position();
+        channel.write(zero());
+        channel.position(position);
+      }
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  /**
+   * Returns a zeroed out byte buffer.
+   */
+  private ByteBuffer zero() {
+    memory.clear();
+    for (int i = 0; i < memory.limit(); i++) {
+      memory.put(i, (byte) 0);
+    }
+    return memory;
+  }
+
+  @Override
+  public void flush() {
+    try {
+      if (channel.isOpen()) {
+        channel.force(true);
+      }
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  @Override
+  public void close() {
+    flush();
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/Indexed.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/Indexed.java
new file mode 100644 (file)
index 0000000..bfbd8cb
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Indexed journal entry.
+ */
+public class Indexed<E> {
+  private final long index;
+  private final E entry;
+  private final int size;
+
+  public Indexed(long index, E entry, int size) {
+    this.index = index;
+    this.entry = entry;
+    this.size = size;
+  }
+
+  /**
+   * Returns the entry index.
+   *
+   * @return The entry index.
+   */
+  public long index() {
+    return index;
+  }
+
+  /**
+   * Returns the indexed entry.
+   *
+   * @return The indexed entry.
+   */
+  public E entry() {
+    return entry;
+  }
+
+  /**
+   * Returns the serialized entry size.
+   *
+   * @return The serialized entry size.
+   */
+  public int size() {
+    return size;
+  }
+
+  /**
+   * Returns the entry type class.
+   *
+   * @return The entry class.
+   */
+  public Class<?> type() {
+    return entry.getClass();
+  }
+
+  /**
+   * Casts the entry to the given type.
+   *
+   * @return The cast entry.
+   */
+  @SuppressWarnings("unchecked")
+  public <E> Indexed<E> cast() {
+    return (Indexed<E>) this;
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("index", index)
+        .add("entry", entry)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/Journal.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/Journal.java
new file mode 100644 (file)
index 0000000..33231ae
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import java.io.Closeable;
+
+/**
+ * Journal.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface Journal<E> extends Closeable {
+
+  /**
+   * Returns the journal writer.
+   *
+   * @return The journal writer.
+   */
+  JournalWriter<E> writer();
+
+  /**
+   * Opens a new journal reader.
+   *
+   * @param index The index at which to start the reader.
+   * @return A new journal reader.
+   */
+  JournalReader<E> openReader(long index);
+
+  /**
+   * Opens a new journal reader.
+   *
+   * @param index The index at which to start the reader.
+   * @param mode the reader mode
+   * @return A new journal reader.
+   */
+  JournalReader<E> openReader(long index, JournalReader.Mode mode);
+
+  /**
+   * Returns a boolean indicating whether the journal is open.
+   *
+   * @return Indicates whether the journal is open.
+   */
+  boolean isOpen();
+
+  @Override
+  void close();
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalReader.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalReader.java
new file mode 100644 (file)
index 0000000..ed20474
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import java.util.Iterator;
+
+/**
+ * Log reader.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface JournalReader<E> extends Iterator<Indexed<E>>, AutoCloseable {
+
+  /**
+   * Raft log reader mode.
+   */
+  enum Mode {
+
+    /**
+     * Reads all entries from the log.
+     */
+    ALL,
+
+    /**
+     * Reads committed entries from the log.
+     */
+    COMMITS,
+  }
+
+  /**
+   * Returns the first index in the journal.
+   *
+   * @return the first index in the journal
+   */
+  long getFirstIndex();
+
+  /**
+   * Returns the current reader index.
+   *
+   * @return The current reader index.
+   */
+  long getCurrentIndex();
+
+  /**
+   * Returns the last read entry.
+   *
+   * @return The last read entry.
+   */
+  Indexed<E> getCurrentEntry();
+
+  /**
+   * Returns the next reader index.
+   *
+   * @return The next reader index.
+   */
+  long getNextIndex();
+
+  /**
+   * Returns whether the reader has a next entry to read.
+   *
+   * @return Whether the reader has a next entry to read.
+   */
+  @Override
+  boolean hasNext();
+
+  /**
+   * Returns the next entry in the reader.
+   *
+   * @return The next entry in the reader.
+   */
+  @Override
+  Indexed<E> next();
+
+  /**
+   * Resets the reader to the start.
+   */
+  void reset();
+
+  /**
+   * Resets the reader to the given index.
+   *
+   * @param index The index to which to reset the reader.
+   */
+  void reset(long index);
+
+  @Override
+  void close();
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalSegment.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalSegment.java
new file mode 100644 (file)
index 0000000..767c35e
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import com.google.common.collect.Sets;
+import io.atomix.storage.StorageException;
+import io.atomix.storage.StorageLevel;
+import io.atomix.storage.journal.index.JournalIndex;
+import io.atomix.storage.journal.index.SparseJournalIndex;
+import io.atomix.utils.serializer.Namespace;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Log segment.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class JournalSegment<E> implements AutoCloseable {
+  private final JournalSegmentFile file;
+  private final JournalSegmentDescriptor descriptor;
+  private final StorageLevel storageLevel;
+  private final int maxEntrySize;
+  private final JournalIndex index;
+  private final Namespace namespace;
+  private final MappableJournalSegmentWriter<E> writer;
+  private final Set<MappableJournalSegmentReader<E>> readers = Sets.newConcurrentHashSet();
+  private final AtomicInteger references = new AtomicInteger();
+  private boolean open = true;
+
+  public JournalSegment(
+      JournalSegmentFile file,
+      JournalSegmentDescriptor descriptor,
+      StorageLevel storageLevel,
+      int maxEntrySize,
+      double indexDensity,
+      Namespace namespace) {
+    this.file = file;
+    this.descriptor = descriptor;
+    this.storageLevel = storageLevel;
+    this.maxEntrySize = maxEntrySize;
+    this.index = new SparseJournalIndex(indexDensity);
+    this.namespace = namespace;
+    this.writer = new MappableJournalSegmentWriter<>(openChannel(file.file()), this, maxEntrySize, index, namespace);
+  }
+
+  private FileChannel openChannel(File file) {
+    try {
+      return FileChannel.open(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  /**
+   * Returns the segment ID.
+   *
+   * @return The segment ID.
+   */
+  public long id() {
+    return descriptor.id();
+  }
+
+  /**
+   * Returns the segment version.
+   *
+   * @return The segment version.
+   */
+  public long version() {
+    return descriptor.version();
+  }
+
+  /**
+   * Returns the segment's starting index.
+   *
+   * @return The segment's starting index.
+   */
+  public long index() {
+    return descriptor.index();
+  }
+
+  /**
+   * Returns the last index in the segment.
+   *
+   * @return The last index in the segment.
+   */
+  public long lastIndex() {
+    return writer.getLastIndex();
+  }
+
+  /**
+   * Returns the size of the segment.
+   *
+   * @return the size of the segment
+   */
+  public int size() {
+    return writer.size();
+  }
+
+  /**
+   * Returns the segment file.
+   *
+   * @return The segment file.
+   */
+  public JournalSegmentFile file() {
+    return file;
+  }
+
+  /**
+   * Returns the segment descriptor.
+   *
+   * @return The segment descriptor.
+   */
+  public JournalSegmentDescriptor descriptor() {
+    return descriptor;
+  }
+
+  /**
+   * Returns a boolean value indicating whether the segment is empty.
+   *
+   * @return Indicates whether the segment is empty.
+   */
+  public boolean isEmpty() {
+    return length() == 0;
+  }
+
+  /**
+   * Returns the segment length.
+   *
+   * @return The segment length.
+   */
+  public long length() {
+    return writer.getNextIndex() - index();
+  }
+
+  /**
+   * Acquires a reference to the log segment.
+   */
+  void acquire() {
+    if (references.getAndIncrement() == 0 && open) {
+      map();
+    }
+  }
+
+  /**
+   * Releases a reference to the log segment.
+   */
+  void release() {
+    if (references.decrementAndGet() == 0 && open) {
+      unmap();
+    }
+  }
+
+  /**
+   * Maps the log segment into memory.
+   */
+  private void map() {
+    if (storageLevel == StorageLevel.MAPPED) {
+      MappedByteBuffer buffer = writer.map();
+      readers.forEach(reader -> reader.map(buffer));
+    }
+  }
+
+  /**
+   * Unmaps the log segment from memory.
+   */
+  private void unmap() {
+    if (storageLevel == StorageLevel.MAPPED) {
+      writer.unmap();
+      readers.forEach(reader -> reader.unmap());
+    }
+  }
+
+  /**
+   * Returns the segment writer.
+   *
+   * @return The segment writer.
+   */
+  public MappableJournalSegmentWriter<E> writer() {
+    checkOpen();
+    return writer;
+  }
+
+  /**
+   * Creates a new segment reader.
+   *
+   * @return A new segment reader.
+   */
+  MappableJournalSegmentReader<E> createReader() {
+    checkOpen();
+    MappableJournalSegmentReader<E> reader = new MappableJournalSegmentReader<>(
+        openChannel(file.file()), this, maxEntrySize, index, namespace);
+    MappedByteBuffer buffer = writer.buffer();
+    if (buffer != null) {
+      reader.map(buffer);
+    }
+    readers.add(reader);
+    return reader;
+  }
+
+  /**
+   * Closes a segment reader.
+   *
+   * @param reader the closed segment reader
+   */
+  void closeReader(MappableJournalSegmentReader<E> reader) {
+    readers.remove(reader);
+  }
+
+  /**
+   * Checks whether the segment is open.
+   */
+  private void checkOpen() {
+    checkState(open, "Segment not open");
+  }
+
+  /**
+   * Returns a boolean indicating whether the segment is open.
+   *
+   * @return indicates whether the segment is open
+   */
+  public boolean isOpen() {
+    return open;
+  }
+
+  /**
+   * Closes the segment.
+   */
+  @Override
+  public void close() {
+    unmap();
+    writer.close();
+    readers.forEach(reader -> reader.close());
+    open = false;
+  }
+
+  /**
+   * Deletes the segment.
+   */
+  public void delete() {
+    try {
+      Files.deleteIfExists(file.file().toPath());
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("id", id())
+        .add("version", version())
+        .add("index", index())
+        .toString();
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalSegmentDescriptor.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalSegmentDescriptor.java
new file mode 100644 (file)
index 0000000..71ad15e
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Stores information about a {@link JournalSegment} of the log.
+ * <p>
+ * The segment descriptor manages metadata related to a single segment of the log. Descriptors are stored within the
+ * first {@code 64} bytes of each segment in the following order:
+ * <ul>
+ * <li>{@code id} (64-bit signed integer) - A unique segment identifier. This is a monotonically increasing number within
+ * each log. Segments with in-sequence identifiers should contain in-sequence indexes.</li>
+ * <li>{@code index} (64-bit signed integer) - The effective first index of the segment. This indicates the index at which
+ * the first entry should be written to the segment. Indexes are monotonically increasing thereafter.</li>
+ * <li>{@code version} (64-bit signed integer) - The version of the segment. Versions are monotonically increasing
+ * starting at {@code 1}. Versions will only be incremented whenever the segment is rewritten to another memory/disk
+ * space, e.g. after log compaction.</li>
+ * <li>{@code maxSegmentSize} (32-bit unsigned integer) - The maximum number of bytes allowed in the segment.</li>
+ * <li>{@code maxEntries} (32-bit signed integer) - The total number of expected entries in the segment. This is the final
+ * number of entries allowed within the segment both before and after compaction. This entry count is used to determine
+ * the count of internal indexing and deduplication facilities.</li>
+ * <li>{@code updated} (64-bit signed integer) - The last update to the segment in terms of milliseconds since the epoch.
+ * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment have
+ * been committed, the {@code updated} time should be set to the current time. Log compaction should not result in a
+ * change to {@code updated}.</li>
+ * <li>{@code locked} (8-bit boolean) - A boolean indicating whether the segment is locked. Segments will be locked once
+ * all entries have been committed to the segment. The lock state of each segment is used to determine log compaction
+ * and recovery behavior.</li>
+ * </ul>
+ * The remainder of the 64 segment header bytes are reserved for future metadata.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public final class JournalSegmentDescriptor {
+  public static final int BYTES = 64;
+
+  // Current segment version.
+  @VisibleForTesting
+  static final int VERSION = 1;
+
+  // The lengths of each field in the header.
+  private static final int VERSION_LENGTH = Integer.BYTES;     // 32-bit signed integer
+  private static final int ID_LENGTH = Long.BYTES;             // 64-bit signed integer
+  private static final int INDEX_LENGTH = Long.BYTES;          // 64-bit signed integer
+  private static final int MAX_SIZE_LENGTH = Integer.BYTES;    // 32-bit signed integer
+  private static final int MAX_ENTRIES_LENGTH = Integer.BYTES; // 32-bit signed integer
+  private static final int UPDATED_LENGTH = Long.BYTES;        // 64-bit signed integer
+
+  // The positions of each field in the header.
+  private static final int VERSION_POSITION = 0;                                         // 0
+  private static final int ID_POSITION = VERSION_POSITION + VERSION_LENGTH;              // 4
+  private static final int INDEX_POSITION = ID_POSITION + ID_LENGTH;                     // 12
+  private static final int MAX_SIZE_POSITION = INDEX_POSITION + INDEX_LENGTH;            // 20
+  private static final int MAX_ENTRIES_POSITION = MAX_SIZE_POSITION + MAX_SIZE_LENGTH;   // 24
+  private static final int UPDATED_POSITION = MAX_ENTRIES_POSITION + MAX_ENTRIES_LENGTH; // 28
+
+  /**
+   * Returns a descriptor builder.
+   * <p>
+   * The descriptor builder will write segment metadata to a {@code 48} byte in-memory buffer.
+   *
+   * @return The descriptor builder.
+   */
+  public static Builder builder() {
+    return new Builder(ByteBuffer.allocate(BYTES));
+  }
+
+  /**
+   * Returns a descriptor builder for the given descriptor buffer.
+   *
+   * @param buffer The descriptor buffer.
+   * @return The descriptor builder.
+   * @throws NullPointerException if {@code buffer} is null
+   */
+  public static Builder builder(ByteBuffer buffer) {
+    return new Builder(buffer);
+  }
+
+  private final ByteBuffer buffer;
+  private final int version;
+  private final long id;
+  private final long index;
+  private final int maxSegmentSize;
+  private final int maxEntries;
+  private volatile long updated;
+  private volatile boolean locked;
+
+  /**
+   * @throws NullPointerException if {@code buffer} is null
+   */
+  public JournalSegmentDescriptor(ByteBuffer buffer) {
+    this.buffer = buffer;
+    this.version = buffer.getInt();
+    this.id = buffer.getLong();
+    this.index = buffer.getLong();
+    this.maxSegmentSize = buffer.getInt();
+    this.maxEntries = buffer.getInt();
+    this.updated = buffer.getLong();
+    this.locked = buffer.get() == 1;
+  }
+
+  /**
+   * Returns the segment version.
+   * <p>
+   * Versions are monotonically increasing starting at {@code 1}.
+   *
+   * @return The segment version.
+   */
+  public int version() {
+    return version;
+  }
+
+  /**
+   * Returns the segment identifier.
+   * <p>
+   * The segment ID is a monotonically increasing number within each log. Segments with in-sequence identifiers should
+   * contain in-sequence indexes.
+   *
+   * @return The segment identifier.
+   */
+  public long id() {
+    return id;
+  }
+
+  /**
+   * Returns the segment index.
+   * <p>
+   * The index indicates the index at which the first entry should be written to the segment. Indexes are monotonically
+   * increasing thereafter.
+   *
+   * @return The segment index.
+   */
+  public long index() {
+    return index;
+  }
+
+  /**
+   * Returns the maximum count of the segment.
+   *
+   * @return The maximum allowed count of the segment.
+   */
+  public int maxSegmentSize() {
+    return maxSegmentSize;
+  }
+
+  /**
+   * Returns the maximum number of entries allowed in the segment.
+   *
+   * @return The maximum number of entries allowed in the segment.
+   */
+  public int maxEntries() {
+    return maxEntries;
+  }
+
+  /**
+   * Returns last time the segment was updated.
+   * <p>
+   * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment have
+   * been committed, the {@code updated} time should be set to the current time. Log compaction should not result in a
+   * change to {@code updated}.
+   *
+   * @return The last time the segment was updated in terms of milliseconds since the epoch.
+   */
+  public long updated() {
+    return updated;
+  }
+
+  /**
+   * Writes an update to the descriptor.
+   */
+  public void update(long timestamp) {
+    if (!locked) {
+      buffer.putLong(UPDATED_POSITION, timestamp);
+      this.updated = timestamp;
+    }
+  }
+
+  /**
+   * Copies the segment to a new buffer.
+   */
+  JournalSegmentDescriptor copyTo(ByteBuffer buffer) {
+    buffer.putInt(version);
+    buffer.putLong(id);
+    buffer.putLong(index);
+    buffer.putInt(maxSegmentSize);
+    buffer.putInt(maxEntries);
+    buffer.putLong(updated);
+    buffer.put(locked ? (byte) 1 : (byte) 0);
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("version", version)
+        .add("id", id)
+        .add("index", index)
+        .add("updated", updated)
+        .toString();
+  }
+
+  /**
+   * Segment descriptor builder.
+   */
+  public static class Builder {
+    private final ByteBuffer buffer;
+
+    private Builder(ByteBuffer buffer) {
+      this.buffer = checkNotNull(buffer, "buffer cannot be null");
+      buffer.putInt(VERSION_POSITION, VERSION);
+    }
+
+    /**
+     * Sets the segment identifier.
+     *
+     * @param id The segment identifier.
+     * @return The segment descriptor builder.
+     */
+    public Builder withId(long id) {
+      buffer.putLong(ID_POSITION, id);
+      return this;
+    }
+
+    /**
+     * Sets the segment index.
+     *
+     * @param index The segment starting index.
+     * @return The segment descriptor builder.
+     */
+    public Builder withIndex(long index) {
+      buffer.putLong(INDEX_POSITION, index);
+      return this;
+    }
+
+    /**
+     * Sets maximum count of the segment.
+     *
+     * @param maxSegmentSize The maximum count of the segment.
+     * @return The segment descriptor builder.
+     */
+    public Builder withMaxSegmentSize(int maxSegmentSize) {
+      buffer.putInt(MAX_SIZE_POSITION, maxSegmentSize);
+      return this;
+    }
+
+    /**
+     * Sets the maximum number of entries in the segment.
+     *
+     * @param maxEntries The maximum number of entries in the segment.
+     * @return The segment descriptor builder.
+     * @deprecated since 3.0.2
+     */
+    @Deprecated
+    public Builder withMaxEntries(int maxEntries) {
+      buffer.putInt(MAX_ENTRIES_POSITION, maxEntries);
+      return this;
+    }
+
+    /**
+     * Builds the segment descriptor.
+     *
+     * @return The built segment descriptor.
+     */
+    public JournalSegmentDescriptor build() {
+      buffer.rewind();
+      return new JournalSegmentDescriptor(buffer);
+    }
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalSegmentFile.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalSegmentFile.java
new file mode 100644 (file)
index 0000000..9fe7f12
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import java.io.File;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Segment file utility.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public final class JournalSegmentFile {
+  private static final char PART_SEPARATOR = '-';
+  private static final char EXTENSION_SEPARATOR = '.';
+  private static final String EXTENSION = "log";
+  private final File file;
+
+  /**
+   * Returns a boolean value indicating whether the given file appears to be a parsable segment file.
+   *
+   * @throws NullPointerException if {@code file} is null
+   */
+  public static boolean isSegmentFile(String name, File file) {
+    return isSegmentFile(name, file.getName());
+  }
+
+  /**
+   * Returns a boolean value indicating whether the given file appears to be a parsable segment file.
+   *
+   * @param journalName the name of the journal
+   * @param fileName the name of the file to check
+   * @throws NullPointerException if {@code file} is null
+   */
+  public static boolean isSegmentFile(String journalName, String fileName) {
+    checkNotNull(journalName, "journalName cannot be null");
+    checkNotNull(fileName, "fileName cannot be null");
+
+    int partSeparator = fileName.lastIndexOf(PART_SEPARATOR);
+    int extensionSeparator = fileName.lastIndexOf(EXTENSION_SEPARATOR);
+
+    if (extensionSeparator == -1
+        || partSeparator == -1
+        || extensionSeparator < partSeparator
+        || !fileName.endsWith(EXTENSION)) {
+      return false;
+    }
+
+    for (int i = partSeparator + 1; i < extensionSeparator; i++) {
+      if (!Character.isDigit(fileName.charAt(i))) {
+        return false;
+      }
+    }
+
+    return fileName.startsWith(journalName);
+  }
+
+  /**
+   * Creates a segment file for the given directory, log name, segment ID, and segment version.
+   */
+  static File createSegmentFile(String name, File directory, long id) {
+    return new File(directory, String.format("%s-%d.log", checkNotNull(name, "name cannot be null"), id));
+  }
+
+  /**
+   * @throws IllegalArgumentException if {@code file} is not a valid segment file
+   */
+  JournalSegmentFile(File file) {
+    this.file = file;
+  }
+
+  /**
+   * Returns the segment file.
+   *
+   * @return The segment file.
+   */
+  public File file() {
+    return file;
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalWriter.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/JournalWriter.java
new file mode 100644 (file)
index 0000000..efb566e
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+/**
+ * Log writer.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface JournalWriter<E> extends AutoCloseable {
+
+  /**
+   * Returns the last written index.
+   *
+   * @return The last written index.
+   */
+  long getLastIndex();
+
+  /**
+   * Returns the last entry written.
+   *
+   * @return The last entry written.
+   */
+  Indexed<E> getLastEntry();
+
+  /**
+   * Returns the next index to be written.
+   *
+   * @return The next index to be written.
+   */
+  long getNextIndex();
+
+  /**
+   * Appends an entry to the journal.
+   *
+   * @param entry The entry to append.
+   * @return The appended indexed entry.
+   */
+  <T extends E> Indexed<T> append(T entry);
+
+  /**
+   * Appends an indexed entry to the log.
+   *
+   * @param entry The indexed entry to append.
+   */
+  void append(Indexed<E> entry);
+
+  /**
+   * Commits entries up to the given index.
+   *
+   * @param index The index up to which to commit entries.
+   */
+  void commit(long index);
+
+  /**
+   * Resets the head of the journal to the given index.
+   *
+   * @param index the index to which to reset the head of the journal
+   */
+  void reset(long index);
+
+  /**
+   * Truncates the log to the given index.
+   *
+   * @param index The index to which to truncate the log.
+   */
+  void truncate(long index);
+
+  /**
+   * Flushes written entries to disk.
+   */
+  void flush();
+
+  @Override
+  void close();
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappableJournalSegmentReader.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappableJournalSegmentReader.java
new file mode 100644 (file)
index 0000000..a48184a
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import io.atomix.storage.StorageException;
+import io.atomix.storage.journal.index.JournalIndex;
+import io.atomix.utils.serializer.Namespace;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Mappable log segment reader.
+ */
+class MappableJournalSegmentReader<E> implements JournalReader<E> {
+  private final JournalSegment<E> segment;
+  private final FileChannel channel;
+  private final int maxEntrySize;
+  private final JournalIndex index;
+  private final Namespace namespace;
+  private JournalReader<E> reader;
+
+  MappableJournalSegmentReader(
+      FileChannel channel,
+      JournalSegment<E> segment,
+      int maxEntrySize,
+      JournalIndex index,
+      Namespace namespace) {
+    this.channel = channel;
+    this.segment = segment;
+    this.maxEntrySize = maxEntrySize;
+    this.index = index;
+    this.namespace = namespace;
+    this.reader = new FileChannelJournalSegmentReader<>(channel, segment, maxEntrySize, index, namespace);
+  }
+
+  /**
+   * Converts the reader to a mapped reader using the given buffer.
+   *
+   * @param buffer the mapped buffer
+   */
+  void map(ByteBuffer buffer) {
+    if (!(reader instanceof MappedJournalSegmentReader)) {
+      JournalReader<E> reader = this.reader;
+      this.reader = new MappedJournalSegmentReader<>(buffer, segment, maxEntrySize, index, namespace);
+      this.reader.reset(reader.getNextIndex());
+      reader.close();
+    }
+  }
+
+  /**
+   * Converts the reader to an unmapped reader.
+   */
+  void unmap() {
+    if (reader instanceof MappedJournalSegmentReader) {
+      JournalReader<E> reader = this.reader;
+      this.reader = new FileChannelJournalSegmentReader<>(channel, segment, maxEntrySize, index, namespace);
+      this.reader.reset(reader.getNextIndex());
+      reader.close();
+    }
+  }
+
+  @Override
+  public long getFirstIndex() {
+    return reader.getFirstIndex();
+  }
+
+  @Override
+  public long getCurrentIndex() {
+    return reader.getCurrentIndex();
+  }
+
+  @Override
+  public Indexed<E> getCurrentEntry() {
+    return reader.getCurrentEntry();
+  }
+
+  @Override
+  public long getNextIndex() {
+    return reader.getNextIndex();
+  }
+
+  @Override
+  public boolean hasNext() {
+    return reader.hasNext();
+  }
+
+  @Override
+  public Indexed<E> next() {
+    return reader.next();
+  }
+
+  @Override
+  public void reset() {
+    reader.reset();
+  }
+
+  @Override
+  public void reset(long index) {
+    reader.reset(index);
+  }
+
+  @Override
+  public void close() {
+    reader.close();
+    try {
+      channel.close();
+    } catch (IOException e) {
+      throw new StorageException(e);
+    } finally {
+      segment.closeReader(this);
+    }
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappableJournalSegmentWriter.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappableJournalSegmentWriter.java
new file mode 100644 (file)
index 0000000..1ca8924
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import io.atomix.storage.StorageException;
+import io.atomix.storage.journal.index.JournalIndex;
+import io.atomix.utils.serializer.Namespace;
+
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Mappable log segment writer.
+ */
+class MappableJournalSegmentWriter<E> implements JournalWriter<E> {
+  private final FileChannel channel;
+  private final JournalSegment<E> segment;
+  private final int maxEntrySize;
+  private final JournalIndex index;
+  private final Namespace namespace;
+  private JournalWriter<E> writer;
+
+  MappableJournalSegmentWriter(
+      FileChannel channel,
+      JournalSegment<E> segment,
+      int maxEntrySize,
+      JournalIndex index,
+      Namespace namespace) {
+    this.channel = channel;
+    this.segment = segment;
+    this.maxEntrySize = maxEntrySize;
+    this.index = index;
+    this.namespace = namespace;
+    this.writer = new FileChannelJournalSegmentWriter<>(channel, segment, maxEntrySize, index, namespace);
+  }
+
+  /**
+   * Maps the segment writer into memory, returning the mapped buffer.
+   *
+   * @return the buffer that was mapped into memory
+   */
+  MappedByteBuffer map() {
+    if (writer instanceof MappedJournalSegmentWriter) {
+      return ((MappedJournalSegmentWriter<E>) writer).buffer();
+    }
+
+    try {
+      JournalWriter<E> writer = this.writer;
+      MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, segment.descriptor().maxSegmentSize());
+      this.writer = new MappedJournalSegmentWriter<>(buffer, segment, maxEntrySize, index, namespace);
+      writer.close();
+      return buffer;
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  /**
+   * Unmaps the mapped buffer.
+   */
+  void unmap() {
+    if (writer instanceof MappedJournalSegmentWriter) {
+      JournalWriter<E> writer = this.writer;
+      this.writer = new FileChannelJournalSegmentWriter<>(channel, segment, maxEntrySize, index, namespace);
+      writer.close();
+    }
+  }
+
+  MappedByteBuffer buffer() {
+    JournalWriter<E> writer = this.writer;
+    if (writer instanceof MappedJournalSegmentWriter) {
+      return ((MappedJournalSegmentWriter<E>) writer).buffer();
+    }
+    return null;
+  }
+
+  /**
+   * Returns the writer's first index.
+   *
+   * @return the writer's first index
+   */
+  public long firstIndex() {
+    return segment.index();
+  }
+
+  /**
+   * Returns the size of the segment.
+   *
+   * @return the size of the segment
+   */
+  public int size() {
+    try {
+      return (int) channel.size();
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  @Override
+  public long getLastIndex() {
+    return writer.getLastIndex();
+  }
+
+  @Override
+  public Indexed<E> getLastEntry() {
+    return writer.getLastEntry();
+  }
+
+  @Override
+  public long getNextIndex() {
+    return writer.getNextIndex();
+  }
+
+  @Override
+  public <T extends E> Indexed<T> append(T entry) {
+    return writer.append(entry);
+  }
+
+  @Override
+  public void append(Indexed<E> entry) {
+    writer.append(entry);
+  }
+
+  @Override
+  public void commit(long index) {
+    writer.commit(index);
+  }
+
+  @Override
+  public void reset(long index) {
+    writer.reset(index);
+  }
+
+  @Override
+  public void truncate(long index) {
+    writer.truncate(index);
+  }
+
+  @Override
+  public void flush() {
+    writer.flush();
+  }
+
+  @Override
+  public void close() {
+    writer.close();
+    try {
+      channel.close();
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappedJournalSegmentReader.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappedJournalSegmentReader.java
new file mode 100644 (file)
index 0000000..cdc65c8
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import io.atomix.storage.journal.index.JournalIndex;
+import io.atomix.storage.journal.index.Position;
+import io.atomix.utils.serializer.Namespace;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.NoSuchElementException;
+import java.util.zip.CRC32;
+
+/**
+ * Log segment reader.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+class MappedJournalSegmentReader<E> implements JournalReader<E> {
+  private final ByteBuffer buffer;
+  private final int maxEntrySize;
+  private final JournalIndex index;
+  private final Namespace namespace;
+  private final long firstIndex;
+  private Indexed<E> currentEntry;
+  private Indexed<E> nextEntry;
+
+  MappedJournalSegmentReader(
+      ByteBuffer buffer,
+      JournalSegment<E> segment,
+      int maxEntrySize,
+      JournalIndex index,
+      Namespace namespace) {
+    this.buffer = buffer.slice();
+    this.maxEntrySize = maxEntrySize;
+    this.index = index;
+    this.namespace = namespace;
+    this.firstIndex = segment.index();
+    reset();
+  }
+
+  @Override
+  public long getFirstIndex() {
+    return firstIndex;
+  }
+
+  @Override
+  public long getCurrentIndex() {
+    return currentEntry != null ? currentEntry.index() : 0;
+  }
+
+  @Override
+  public Indexed<E> getCurrentEntry() {
+    return currentEntry;
+  }
+
+  @Override
+  public long getNextIndex() {
+    return currentEntry != null ? currentEntry.index() + 1 : firstIndex;
+  }
+
+  @Override
+  public void reset(long index) {
+    reset();
+    Position position = this.index.lookup(index - 1);
+    if (position != null) {
+      currentEntry = new Indexed<>(position.index() - 1, null, 0);
+      buffer.position(position.position());
+      readNext();
+    }
+    while (getNextIndex() < index && hasNext()) {
+      next();
+    }
+  }
+
+  @Override
+  public void reset() {
+    buffer.position(JournalSegmentDescriptor.BYTES);
+    currentEntry = null;
+    nextEntry = null;
+    readNext();
+  }
+
+  @Override
+  public boolean hasNext() {
+    // If the next entry is null, check whether a next entry exists.
+    if (nextEntry == null) {
+      readNext();
+    }
+    return nextEntry != null;
+  }
+
+  @Override
+  public Indexed<E> next() {
+    if (!hasNext()) {
+      throw new NoSuchElementException();
+    }
+
+    // Set the current entry to the next entry.
+    currentEntry = nextEntry;
+
+    // Reset the next entry to null.
+    nextEntry = null;
+
+    // Read the next entry in the segment.
+    readNext();
+
+    // Return the current entry.
+    return currentEntry;
+  }
+
+  /**
+   * Reads the next entry in the segment.
+   */
+  @SuppressWarnings("unchecked")
+  private void readNext() {
+    // Compute the index of the next entry in the segment.
+    final long index = getNextIndex();
+
+    // Mark the buffer so it can be reset if necessary.
+    buffer.mark();
+
+    try {
+      // Read the length of the entry.
+      final int length = buffer.getInt();
+
+      // If the buffer length is zero then return.
+      if (length <= 0 || length > maxEntrySize) {
+        buffer.reset();
+        nextEntry = null;
+        return;
+      }
+
+      // Read the checksum of the entry.
+      long checksum = buffer.getInt() & 0xFFFFFFFFL;
+
+      // Compute the checksum for the entry bytes.
+      final CRC32 crc32 = new CRC32();
+      ByteBuffer slice = buffer.slice();
+      slice.limit(length);
+      crc32.update(slice);
+
+      // If the stored checksum equals the computed checksum, return the entry.
+      if (checksum == crc32.getValue()) {
+        slice.rewind();
+        E entry = namespace.deserialize(slice);
+        nextEntry = new Indexed<>(index, entry, length);
+        buffer.position(buffer.position() + length);
+      } else {
+        buffer.reset();
+        nextEntry = null;
+      }
+    } catch (BufferUnderflowException e) {
+      buffer.reset();
+      nextEntry = null;
+    }
+  }
+
+  @Override
+  public void close() {
+    // Do nothing. The writer is responsible for cleaning the mapped buffer.
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappedJournalSegmentWriter.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/MappedJournalSegmentWriter.java
new file mode 100644 (file)
index 0000000..27846fa
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import com.esotericsoftware.kryo.KryoException;
+import io.atomix.storage.StorageException;
+import io.atomix.storage.journal.index.JournalIndex;
+import io.atomix.utils.memory.BufferCleaner;
+import io.atomix.utils.serializer.Namespace;
+
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.util.zip.CRC32;
+
+/**
+ * Segment writer.
+ * <p>
+ * The format of an entry in the log is as follows:
+ * <ul>
+ * <li>64-bit index</li>
+ * <li>8-bit boolean indicating whether a term change is contained in the entry</li>
+ * <li>64-bit optional term</li>
+ * <li>32-bit signed entry length, including the entry type ID</li>
+ * <li>8-bit signed entry type ID</li>
+ * <li>n-bit entry bytes</li>
+ * </ul>
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+class MappedJournalSegmentWriter<E> implements JournalWriter<E> {
+  private final MappedByteBuffer mappedBuffer;
+  private final ByteBuffer buffer;
+  private final JournalSegment<E> segment;
+  private final int maxEntrySize;
+  private final JournalIndex index;
+  private final Namespace namespace;
+  private final long firstIndex;
+  private Indexed<E> lastEntry;
+
+  MappedJournalSegmentWriter(
+      MappedByteBuffer buffer,
+      JournalSegment<E> segment,
+      int maxEntrySize,
+      JournalIndex index,
+      Namespace namespace) {
+    this.mappedBuffer = buffer;
+    this.buffer = buffer.slice();
+    this.segment = segment;
+    this.maxEntrySize = maxEntrySize;
+    this.index = index;
+    this.namespace = namespace;
+    this.firstIndex = segment.index();
+    reset(0);
+  }
+
+  /**
+   * Returns the mapped buffer underlying the segment writer.
+   *
+   * @return the mapped buffer underlying the segment writer
+   */
+  MappedByteBuffer buffer() {
+    return mappedBuffer;
+  }
+
+  @Override
+  public void reset(long index) {
+    long nextIndex = firstIndex;
+
+    // Clear the buffer indexes.
+    buffer.position(JournalSegmentDescriptor.BYTES);
+
+    // Record the current buffer position.
+    int position = buffer.position();
+
+    // Read the entry length.
+    buffer.mark();
+
+    try {
+      int length = buffer.getInt();
+
+      // If the length is non-zero, read the entry.
+      while (0 < length && length <= maxEntrySize && (index == 0 || nextIndex <= index)) {
+
+        // Read the checksum of the entry.
+        final long checksum = buffer.getInt() & 0xFFFFFFFFL;
+
+        // Compute the checksum for the entry bytes.
+        final CRC32 crc32 = new CRC32();
+        ByteBuffer slice = buffer.slice();
+        slice.limit(length);
+        crc32.update(slice);
+
+        // If the stored checksum equals the computed checksum, return the entry.
+        if (checksum == crc32.getValue()) {
+          slice.rewind();
+          final E entry = namespace.deserialize(slice);
+          lastEntry = new Indexed<>(nextIndex, entry, length);
+          this.index.index(nextIndex, position);
+          nextIndex++;
+        } else {
+          break;
+        }
+
+        // Update the current position for indexing.
+        position = buffer.position() + length;
+        buffer.position(position);
+
+        buffer.mark();
+        length = buffer.getInt();
+      }
+
+      // Reset the buffer to the previous mark.
+      buffer.reset();
+    } catch (BufferUnderflowException e) {
+      buffer.reset();
+    }
+  }
+
+  @Override
+  public long getLastIndex() {
+    return lastEntry != null ? lastEntry.index() : segment.index() - 1;
+  }
+
+  @Override
+  public Indexed<E> getLastEntry() {
+    return lastEntry;
+  }
+
+  @Override
+  public long getNextIndex() {
+    if (lastEntry != null) {
+      return lastEntry.index() + 1;
+    } else {
+      return firstIndex;
+    }
+  }
+
+  /**
+   * Returns the size of the underlying buffer.
+   *
+   * @return The size of the underlying buffer.
+   */
+  public long size() {
+    return buffer.position() + JournalSegmentDescriptor.BYTES;
+  }
+
+  /**
+   * Returns a boolean indicating whether the segment is empty.
+   *
+   * @return Indicates whether the segment is empty.
+   */
+  public boolean isEmpty() {
+    return lastEntry == null;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public void append(Indexed<E> entry) {
+    final long nextIndex = getNextIndex();
+
+    // If the entry's index is greater than the next index in the segment, skip some entries.
+    if (entry.index() > nextIndex) {
+      throw new IndexOutOfBoundsException("Entry index is not sequential");
+    }
+
+    // If the entry's index is less than the next index, truncate the segment.
+    if (entry.index() < nextIndex) {
+      truncate(entry.index() - 1);
+    }
+    append(entry.entry());
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T extends E> Indexed<T> append(T entry) {
+    // Store the entry index.
+    final long index = getNextIndex();
+
+    // Serialize the entry.
+    int position = buffer.position();
+    if (position + Integer.BYTES + Integer.BYTES > buffer.limit()) {
+      throw new BufferOverflowException();
+    }
+
+    buffer.position(position + Integer.BYTES + Integer.BYTES);
+
+    try {
+      namespace.serialize(entry, buffer);
+    } catch (KryoException e) {
+      throw new BufferOverflowException();
+    }
+
+    final int length = buffer.position() - (position + Integer.BYTES + Integer.BYTES);
+
+    // If the entry length exceeds the maximum entry size then throw an exception.
+    if (length > maxEntrySize) {
+      // Just reset the buffer. There's no need to zero the bytes since we haven't written the length or checksum.
+      buffer.position(position);
+      throw new StorageException.TooLarge("Entry size " + length + " exceeds maximum allowed bytes (" + maxEntrySize + ")");
+    }
+
+    // Compute the checksum for the entry.
+    final CRC32 crc32 = new CRC32();
+    buffer.position(position + Integer.BYTES + Integer.BYTES);
+    ByteBuffer slice = buffer.slice();
+    slice.limit(length);
+    crc32.update(slice);
+    final long checksum = crc32.getValue();
+
+    // Create a single byte[] in memory for the entire entry and write it as a batch to the underlying buffer.
+    buffer.position(position);
+    buffer.putInt(length);
+    buffer.putInt((int) checksum);
+    buffer.position(position + Integer.BYTES + Integer.BYTES + length);
+
+    // Update the last entry with the correct index/term/length.
+    Indexed<E> indexedEntry = new Indexed<>(index, entry, length);
+    this.lastEntry = indexedEntry;
+    this.index.index(index, position);
+    return (Indexed<T>) indexedEntry;
+  }
+
+  @Override
+  public void commit(long index) {
+
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public void truncate(long index) {
+    // If the index is greater than or equal to the last index, skip the truncate.
+    if (index >= getLastIndex()) {
+      return;
+    }
+
+    // Reset the last entry.
+    lastEntry = null;
+
+    // Truncate the index.
+    this.index.truncate(index);
+
+    if (index < segment.index()) {
+      buffer.position(JournalSegmentDescriptor.BYTES);
+      buffer.putInt(0);
+      buffer.putInt(0);
+      buffer.position(JournalSegmentDescriptor.BYTES);
+    } else {
+      // Reset the writer to the given index.
+      reset(index);
+
+      // Zero entries after the given index.
+      int position = buffer.position();
+      buffer.putInt(0);
+      buffer.putInt(0);
+      buffer.position(position);
+    }
+  }
+
+  @Override
+  public void flush() {
+    mappedBuffer.force();
+  }
+
+  @Override
+  public void close() {
+    flush();
+    try {
+      BufferCleaner.freeBuffer(mappedBuffer);
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/SegmentedJournal.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/SegmentedJournal.java
new file mode 100644 (file)
index 0000000..07496c5
--- /dev/null
@@ -0,0 +1,885 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.StandardOpenOption;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+
+import com.google.common.collect.Sets;
+import io.atomix.storage.StorageException;
+import io.atomix.storage.StorageLevel;
+import io.atomix.utils.serializer.Namespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Segmented journal.
+ */
+public class SegmentedJournal<E> implements Journal<E> {
+
+  /**
+   * Returns a new Raft log builder.
+   *
+   * @return A new Raft log builder.
+   */
+  public static <E> Builder<E> builder() {
+    return new Builder<>();
+  }
+
+  private static final int SEGMENT_BUFFER_FACTOR = 3;
+
+  private final Logger log = LoggerFactory.getLogger(getClass());
+  private final String name;
+  private final StorageLevel storageLevel;
+  private final File directory;
+  private final Namespace namespace;
+  private final int maxSegmentSize;
+  private final int maxEntrySize;
+  private final int maxEntriesPerSegment;
+  private final double indexDensity;
+  private final boolean flushOnCommit;
+  private final SegmentedJournalWriter<E> writer;
+  private volatile long commitIndex;
+
+  private final NavigableMap<Long, JournalSegment<E>> segments = new ConcurrentSkipListMap<>();
+  private final Collection<SegmentedJournalReader> readers = Sets.newConcurrentHashSet();
+  private JournalSegment<E> currentSegment;
+
+  private volatile boolean open = true;
+
+  public SegmentedJournal(
+      String name,
+      StorageLevel storageLevel,
+      File directory,
+      Namespace namespace,
+      int maxSegmentSize,
+      int maxEntrySize,
+      int maxEntriesPerSegment,
+      double indexDensity,
+      boolean flushOnCommit) {
+    this.name = checkNotNull(name, "name cannot be null");
+    this.storageLevel = checkNotNull(storageLevel, "storageLevel cannot be null");
+    this.directory = checkNotNull(directory, "directory cannot be null");
+    this.namespace = checkNotNull(namespace, "namespace cannot be null");
+    this.maxSegmentSize = maxSegmentSize;
+    this.maxEntrySize = maxEntrySize;
+    this.maxEntriesPerSegment = maxEntriesPerSegment;
+    this.indexDensity = indexDensity;
+    this.flushOnCommit = flushOnCommit;
+    open();
+    this.writer = openWriter();
+  }
+
+  /**
+   * Returns the segment file name prefix.
+   *
+   * @return The segment file name prefix.
+   */
+  public String name() {
+    return name;
+  }
+
+  /**
+   * Returns the storage directory.
+   * <p>
+   * The storage directory is the directory to which all segments write files. Segment files for multiple logs may be
+   * stored in the storage directory, and files for each log instance will be identified by the {@code prefix} provided
+   * when the log is opened.
+   *
+   * @return The storage directory.
+   */
+  public File directory() {
+    return directory;
+  }
+
+  /**
+   * Returns the storage level.
+   * <p>
+   * The storage level dictates how entries within individual journal segments should be stored.
+   *
+   * @return The storage level.
+   */
+  public StorageLevel storageLevel() {
+    return storageLevel;
+  }
+
+  /**
+   * Returns the maximum journal segment size.
+   * <p>
+   * The maximum segment size dictates the maximum size any segment in a segment may consume in bytes.
+   *
+   * @return The maximum segment size in bytes.
+   */
+  public int maxSegmentSize() {
+    return maxSegmentSize;
+  }
+
+  /**
+   * Returns the maximum journal entry size.
+   * <p>
+   * The maximum entry size dictates the maximum size any entry in the segment may consume in bytes.
+   *
+   * @return the maximum entry size in bytes
+   */
+  public int maxEntrySize() {
+    return maxEntrySize;
+  }
+
+  /**
+   * Returns the maximum number of entries per segment.
+   * <p>
+   * The maximum entries per segment dictates the maximum number of entries that are allowed to be stored in any segment
+   * in a journal.
+   *
+   * @return The maximum number of entries per segment.
+   * @deprecated since 3.0.2
+   */
+  @Deprecated
+  public int maxEntriesPerSegment() {
+    return maxEntriesPerSegment;
+  }
+
+  /**
+   * Returns the collection of journal segments.
+   *
+   * @return the collection of journal segments
+   */
+  public Collection<JournalSegment<E>> segments() {
+    return segments.values();
+  }
+
+  /**
+   * Returns the collection of journal segments with indexes greater than the given index.
+   *
+   * @param index the starting index
+   * @return the journal segments starting with indexes greater than or equal to the given index
+   */
+  public Collection<JournalSegment<E>> segments(long index) {
+    return segments.tailMap(index).values();
+  }
+
+  /**
+   * Returns the total size of the journal.
+   *
+   * @return the total size of the journal
+   */
+  public long size() {
+    return segments.values().stream()
+        .mapToLong(segment -> segment.size())
+        .sum();
+  }
+
+  @Override
+  public SegmentedJournalWriter<E> writer() {
+    return writer;
+  }
+
+  @Override
+  public SegmentedJournalReader<E> openReader(long index) {
+    return openReader(index, SegmentedJournalReader.Mode.ALL);
+  }
+
+  /**
+   * Opens a new Raft log reader with the given reader mode.
+   *
+   * @param index The index from which to begin reading entries.
+   * @param mode The mode in which to read entries.
+   * @return The Raft log reader.
+   */
+  public SegmentedJournalReader<E> openReader(long index, SegmentedJournalReader.Mode mode) {
+    SegmentedJournalReader<E> reader = new SegmentedJournalReader<>(this, index, mode);
+    readers.add(reader);
+    return reader;
+  }
+
+  /**
+   * Opens a new journal writer.
+   *
+   * @return A new journal writer.
+   */
+  protected SegmentedJournalWriter<E> openWriter() {
+    return new SegmentedJournalWriter<>(this);
+  }
+
+  /**
+   * Opens the segments.
+   */
+  private void open() {
+    // Load existing log segments from disk.
+    for (JournalSegment<E> segment : loadSegments()) {
+      segments.put(segment.descriptor().index(), segment);
+    }
+
+    // If a segment doesn't already exist, create an initial segment starting at index 1.
+    if (!segments.isEmpty()) {
+      currentSegment = segments.lastEntry().getValue();
+    } else {
+      JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
+          .withId(1)
+          .withIndex(1)
+          .withMaxSegmentSize(maxSegmentSize)
+          .withMaxEntries(maxEntriesPerSegment)
+          .build();
+
+      currentSegment = createSegment(descriptor);
+      currentSegment.descriptor().update(System.currentTimeMillis());
+
+      segments.put(1L, currentSegment);
+    }
+  }
+
+  /**
+   * Asserts that the manager is open.
+   *
+   * @throws IllegalStateException if the segment manager is not open
+   */
+  private void assertOpen() {
+    checkState(currentSegment != null, "journal not open");
+  }
+
+  /**
+   * Asserts that enough disk space is available to allocate a new segment.
+   */
+  private void assertDiskSpace() {
+    if (directory().getUsableSpace() < maxSegmentSize() * SEGMENT_BUFFER_FACTOR) {
+      throw new StorageException.OutOfDiskSpace("Not enough space to allocate a new journal segment");
+    }
+  }
+
+  /**
+   * Resets the current segment, creating a new segment if necessary.
+   */
+  private synchronized void resetCurrentSegment() {
+    JournalSegment<E> lastSegment = getLastSegment();
+    if (lastSegment != null) {
+      currentSegment = lastSegment;
+    } else {
+      JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
+          .withId(1)
+          .withIndex(1)
+          .withMaxSegmentSize(maxSegmentSize)
+          .withMaxEntries(maxEntriesPerSegment)
+          .build();
+
+      currentSegment = createSegment(descriptor);
+
+      segments.put(1L, currentSegment);
+    }
+  }
+
+  /**
+   * Resets and returns the first segment in the journal.
+   *
+   * @param index the starting index of the journal
+   * @return the first segment
+   */
+  JournalSegment<E> resetSegments(long index) {
+    assertOpen();
+
+    // If the index already equals the first segment index, skip the reset.
+    JournalSegment<E> firstSegment = getFirstSegment();
+    if (index == firstSegment.index()) {
+      return firstSegment;
+    }
+
+    for (JournalSegment<E> segment : segments.values()) {
+      segment.close();
+      segment.delete();
+    }
+    segments.clear();
+
+    JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
+        .withId(1)
+        .withIndex(index)
+        .withMaxSegmentSize(maxSegmentSize)
+        .withMaxEntries(maxEntriesPerSegment)
+        .build();
+    currentSegment = createSegment(descriptor);
+    segments.put(index, currentSegment);
+    return currentSegment;
+  }
+
+  /**
+   * Returns the first segment in the log.
+   *
+   * @throws IllegalStateException if the segment manager is not open
+   */
+  JournalSegment<E> getFirstSegment() {
+    assertOpen();
+    Map.Entry<Long, JournalSegment<E>> segment = segments.firstEntry();
+    return segment != null ? segment.getValue() : null;
+  }
+
+  /**
+   * Returns the last segment in the log.
+   *
+   * @throws IllegalStateException if the segment manager is not open
+   */
+  JournalSegment<E> getLastSegment() {
+    assertOpen();
+    Map.Entry<Long, JournalSegment<E>> segment = segments.lastEntry();
+    return segment != null ? segment.getValue() : null;
+  }
+
+  /**
+   * Creates and returns the next segment.
+   *
+   * @return The next segment.
+   * @throws IllegalStateException if the segment manager is not open
+   */
+  synchronized JournalSegment<E> getNextSegment() {
+    assertOpen();
+    assertDiskSpace();
+
+    JournalSegment lastSegment = getLastSegment();
+    JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
+        .withId(lastSegment != null ? lastSegment.descriptor().id() + 1 : 1)
+        .withIndex(currentSegment.lastIndex() + 1)
+        .withMaxSegmentSize(maxSegmentSize)
+        .withMaxEntries(maxEntriesPerSegment)
+        .build();
+
+    currentSegment = createSegment(descriptor);
+
+    segments.put(descriptor.index(), currentSegment);
+    return currentSegment;
+  }
+
+  /**
+   * Returns the segment following the segment with the given ID.
+   *
+   * @param index The segment index with which to look up the next segment.
+   * @return The next segment for the given index.
+   */
+  JournalSegment<E> getNextSegment(long index) {
+    Map.Entry<Long, JournalSegment<E>> nextSegment = segments.higherEntry(index);
+    return nextSegment != null ? nextSegment.getValue() : null;
+  }
+
+  /**
+   * Returns the segment for the given index.
+   *
+   * @param index The index for which to return the segment.
+   * @throws IllegalStateException if the segment manager is not open
+   */
+  synchronized JournalSegment<E> getSegment(long index) {
+    assertOpen();
+    // Check if the current segment contains the given index first in order to prevent an unnecessary map lookup.
+    if (currentSegment != null && index > currentSegment.index()) {
+      return currentSegment;
+    }
+
+    // If the index is in another segment, get the entry with the next lowest first index.
+    Map.Entry<Long, JournalSegment<E>> segment = segments.floorEntry(index);
+    if (segment != null) {
+      return segment.getValue();
+    }
+    return getFirstSegment();
+  }
+
+  /**
+   * Removes a segment.
+   *
+   * @param segment The segment to remove.
+   */
+  synchronized void removeSegment(JournalSegment segment) {
+    segments.remove(segment.index());
+    segment.close();
+    segment.delete();
+    resetCurrentSegment();
+  }
+
+  /**
+   * Creates a new segment.
+   */
+  JournalSegment<E> createSegment(JournalSegmentDescriptor descriptor) {
+    File segmentFile = JournalSegmentFile.createSegmentFile(name, directory, descriptor.id());
+
+    RandomAccessFile raf;
+    FileChannel channel;
+    try {
+      raf = new RandomAccessFile(segmentFile, "rw");
+      raf.setLength(descriptor.maxSegmentSize());
+      channel =  raf.getChannel();
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+
+    ByteBuffer buffer = ByteBuffer.allocate(JournalSegmentDescriptor.BYTES);
+    descriptor.copyTo(buffer);
+    buffer.flip();
+    try {
+      channel.write(buffer);
+    } catch (IOException e) {
+      throw new StorageException(e);
+    } finally {
+      try {
+        channel.close();
+        raf.close();
+      } catch (IOException e) {
+      }
+    }
+    JournalSegment<E> segment = newSegment(new JournalSegmentFile(segmentFile), descriptor);
+    log.debug("Created segment: {}", segment);
+    return segment;
+  }
+
+  /**
+   * Creates a new segment instance.
+   *
+   * @param segmentFile The segment file.
+   * @param descriptor The segment descriptor.
+   * @return The segment instance.
+   */
+  protected JournalSegment<E> newSegment(JournalSegmentFile segmentFile, JournalSegmentDescriptor descriptor) {
+    return new JournalSegment<>(segmentFile, descriptor, storageLevel, maxEntrySize, indexDensity, namespace);
+  }
+
+  /**
+   * Loads a segment.
+   */
+  private JournalSegment<E> loadSegment(long segmentId) {
+    File segmentFile = JournalSegmentFile.createSegmentFile(name, directory, segmentId);
+    ByteBuffer buffer = ByteBuffer.allocate(JournalSegmentDescriptor.BYTES);
+    try (FileChannel channel = openChannel(segmentFile)) {
+      channel.read(buffer);
+      buffer.flip();
+      JournalSegmentDescriptor descriptor = new JournalSegmentDescriptor(buffer);
+      JournalSegment<E> segment = newSegment(new JournalSegmentFile(segmentFile), descriptor);
+      log.debug("Loaded disk segment: {} ({})", descriptor.id(), segmentFile.getName());
+      return segment;
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  private FileChannel openChannel(File file) {
+    try {
+      return FileChannel.open(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  /**
+   * Loads all segments from disk.
+   *
+   * @return A collection of segments for the log.
+   */
+  protected Collection<JournalSegment<E>> loadSegments() {
+    // Ensure log directories are created.
+    directory.mkdirs();
+
+    TreeMap<Long, JournalSegment<E>> segments = new TreeMap<>();
+
+    // Iterate through all files in the log directory.
+    for (File file : directory.listFiles(File::isFile)) {
+
+      // If the file looks like a segment file, attempt to load the segment.
+      if (JournalSegmentFile.isSegmentFile(name, file)) {
+        JournalSegmentFile segmentFile = new JournalSegmentFile(file);
+        ByteBuffer buffer = ByteBuffer.allocate(JournalSegmentDescriptor.BYTES);
+        try (FileChannel channel = openChannel(file)) {
+          channel.read(buffer);
+          buffer.flip();
+        } catch (IOException e) {
+          throw new StorageException(e);
+        }
+
+        JournalSegmentDescriptor descriptor = new JournalSegmentDescriptor(buffer);
+
+        // Load the segment.
+        JournalSegment<E> segment = loadSegment(descriptor.id());
+
+        // Add the segment to the segments list.
+        log.debug("Found segment: {} ({})", segment.descriptor().id(), segmentFile.file().getName());
+        segments.put(segment.index(), segment);
+      }
+    }
+
+    // Verify that all the segments in the log align with one another.
+    JournalSegment<E> previousSegment = null;
+    boolean corrupted = false;
+    Iterator<Map.Entry<Long, JournalSegment<E>>> iterator = segments.entrySet().iterator();
+    while (iterator.hasNext()) {
+      JournalSegment<E> segment = iterator.next().getValue();
+      if (previousSegment != null && previousSegment.lastIndex() != segment.index() - 1) {
+        log.warn("Journal is inconsistent. {} is not aligned with prior segment {}", segment.file().file(), previousSegment.file().file());
+        corrupted = true;
+      }
+      if (corrupted) {
+        segment.close();
+        segment.delete();
+        iterator.remove();
+      }
+      previousSegment = segment;
+    }
+
+    return segments.values();
+  }
+
+  /**
+   * Resets journal readers to the given head.
+   *
+   * @param index The index at which to reset readers.
+   */
+  void resetHead(long index) {
+    for (SegmentedJournalReader reader : readers) {
+      if (reader.getNextIndex() < index) {
+        reader.reset(index);
+      }
+    }
+  }
+
+  /**
+   * Resets journal readers to the given tail.
+   *
+   * @param index The index at which to reset readers.
+   */
+  void resetTail(long index) {
+    for (SegmentedJournalReader reader : readers) {
+      if (reader.getNextIndex() >= index) {
+        reader.reset(index);
+      }
+    }
+  }
+
+  void closeReader(SegmentedJournalReader reader) {
+    readers.remove(reader);
+  }
+
+  @Override
+  public boolean isOpen() {
+    return open;
+  }
+
+  /**
+   * Returns a boolean indicating whether a segment can be removed from the journal prior to the given index.
+   *
+   * @param index the index from which to remove segments
+   * @return indicates whether a segment can be removed from the journal
+   */
+  public boolean isCompactable(long index) {
+    Map.Entry<Long, JournalSegment<E>> segmentEntry = segments.floorEntry(index);
+    return segmentEntry != null && segments.headMap(segmentEntry.getValue().index()).size() > 0;
+  }
+
+  /**
+   * Returns the index of the last segment in the log.
+   *
+   * @param index the compaction index
+   * @return the starting index of the last segment in the log
+   */
+  public long getCompactableIndex(long index) {
+    Map.Entry<Long, JournalSegment<E>> segmentEntry = segments.floorEntry(index);
+    return segmentEntry != null ? segmentEntry.getValue().index() : 0;
+  }
+
+  /**
+   * Compacts the journal up to the given index.
+   * <p>
+   * The semantics of compaction are not specified by this interface.
+   *
+   * @param index The index up to which to compact the journal.
+   */
+  public void compact(long index) {
+    Map.Entry<Long, JournalSegment<E>> segmentEntry = segments.floorEntry(index);
+    if (segmentEntry != null) {
+      SortedMap<Long, JournalSegment<E>> compactSegments = segments.headMap(segmentEntry.getValue().index());
+      if (!compactSegments.isEmpty()) {
+        log.debug("{} - Compacting {} segment(s)", name, compactSegments.size());
+        for (JournalSegment segment : compactSegments.values()) {
+          log.trace("Deleting segment: {}", segment);
+          segment.close();
+          segment.delete();
+        }
+        compactSegments.clear();
+        resetHead(segmentEntry.getValue().index());
+      }
+    }
+  }
+
+  @Override
+  public void close() {
+    segments.values().forEach(segment -> {
+      log.debug("Closing segment: {}", segment);
+      segment.close();
+    });
+    currentSegment = null;
+    open = false;
+  }
+
+  /**
+   * Returns whether {@code flushOnCommit} is enabled for the log.
+   *
+   * @return Indicates whether {@code flushOnCommit} is enabled for the log.
+   */
+  boolean isFlushOnCommit() {
+    return flushOnCommit;
+  }
+
+  /**
+   * Commits entries up to the given index.
+   *
+   * @param index The index up to which to commit entries.
+   */
+  void setCommitIndex(long index) {
+    this.commitIndex = index;
+  }
+
+  /**
+   * Returns the Raft log commit index.
+   *
+   * @return The Raft log commit index.
+   */
+  long getCommitIndex() {
+    return commitIndex;
+  }
+
+  /**
+   * Raft log builder.
+   */
+  public static class Builder<E> implements io.atomix.utils.Builder<SegmentedJournal<E>> {
+    private static final boolean DEFAULT_FLUSH_ON_COMMIT = false;
+    private static final String DEFAULT_NAME = "atomix";
+    private static final String DEFAULT_DIRECTORY = System.getProperty("user.dir");
+    private static final int DEFAULT_MAX_SEGMENT_SIZE = 1024 * 1024 * 32;
+    private static final int DEFAULT_MAX_ENTRY_SIZE = 1024 * 1024;
+    private static final int DEFAULT_MAX_ENTRIES_PER_SEGMENT = 1024 * 1024;
+    private static final double DEFAULT_INDEX_DENSITY = .005;
+    private static final int DEFAULT_CACHE_SIZE = 1024;
+
+    protected String name = DEFAULT_NAME;
+    protected StorageLevel storageLevel = StorageLevel.DISK;
+    protected File directory = new File(DEFAULT_DIRECTORY);
+    protected Namespace namespace;
+    protected int maxSegmentSize = DEFAULT_MAX_SEGMENT_SIZE;
+    protected int maxEntrySize = DEFAULT_MAX_ENTRY_SIZE;
+    protected int maxEntriesPerSegment = DEFAULT_MAX_ENTRIES_PER_SEGMENT;
+    protected double indexDensity = DEFAULT_INDEX_DENSITY;
+    protected int cacheSize = DEFAULT_CACHE_SIZE;
+    private boolean flushOnCommit = DEFAULT_FLUSH_ON_COMMIT;
+
+    protected Builder() {
+    }
+
+    /**
+     * Sets the storage name.
+     *
+     * @param name The storage name.
+     * @return The storage builder.
+     */
+    public Builder<E> withName(String name) {
+      this.name = checkNotNull(name, "name cannot be null");
+      return this;
+    }
+
+    /**
+     * Sets the log storage level, returning the builder for method chaining.
+     * <p>
+     * The storage level indicates how individual entries should be persisted in the journal.
+     *
+     * @param storageLevel The log storage level.
+     * @return The storage builder.
+     */
+    public Builder<E> withStorageLevel(StorageLevel storageLevel) {
+      this.storageLevel = checkNotNull(storageLevel, "storageLevel cannot be null");
+      return this;
+    }
+
+    /**
+     * Sets the log directory, returning the builder for method chaining.
+     * <p>
+     * The log will write segment files into the provided directory.
+     *
+     * @param directory The log directory.
+     * @return The storage builder.
+     * @throws NullPointerException If the {@code directory} is {@code null}
+     */
+    public Builder<E> withDirectory(String directory) {
+      return withDirectory(new File(checkNotNull(directory, "directory cannot be null")));
+    }
+
+    /**
+     * Sets the log directory, returning the builder for method chaining.
+     * <p>
+     * The log will write segment files into the provided directory.
+     *
+     * @param directory The log directory.
+     * @return The storage builder.
+     * @throws NullPointerException If the {@code directory} is {@code null}
+     */
+    public Builder<E> withDirectory(File directory) {
+      this.directory = checkNotNull(directory, "directory cannot be null");
+      return this;
+    }
+
+    /**
+     * Sets the journal namespace, returning the builder for method chaining.
+     *
+     * @param namespace The journal serializer.
+     * @return The journal builder.
+     */
+    public Builder<E> withNamespace(Namespace namespace) {
+      this.namespace = checkNotNull(namespace, "namespace cannot be null");
+      return this;
+    }
+
+    /**
+     * Sets the maximum segment size in bytes, returning the builder for method chaining.
+     * <p>
+     * The maximum segment size dictates when logs should roll over to new segments. As entries are written to a segment
+     * of the log, once the size of the segment surpasses the configured maximum segment size, the log will create a new
+     * segment and append new entries to that segment.
+     * <p>
+     * By default, the maximum segment size is {@code 1024 * 1024 * 32}.
+     *
+     * @param maxSegmentSize The maximum segment size in bytes.
+     * @return The storage builder.
+     * @throws IllegalArgumentException If the {@code maxSegmentSize} is not positive
+     */
+    public Builder<E> withMaxSegmentSize(int maxSegmentSize) {
+      checkArgument(maxSegmentSize > JournalSegmentDescriptor.BYTES, "maxSegmentSize must be greater than " + JournalSegmentDescriptor.BYTES);
+      this.maxSegmentSize = maxSegmentSize;
+      return this;
+    }
+
+    /**
+     * Sets the maximum entry size in bytes, returning the builder for method chaining.
+     *
+     * @param maxEntrySize the maximum entry size in bytes
+     * @return the storage builder
+     * @throws IllegalArgumentException if the {@code maxEntrySize} is not positive
+     */
+    public Builder<E> withMaxEntrySize(int maxEntrySize) {
+      checkArgument(maxEntrySize > 0, "maxEntrySize must be positive");
+      this.maxEntrySize = maxEntrySize;
+      return this;
+    }
+
+    /**
+     * Sets the maximum number of allows entries per segment, returning the builder for method chaining.
+     * <p>
+     * The maximum entry count dictates when logs should roll over to new segments. As entries are written to a segment
+     * of the log, if the entry count in that segment meets the configured maximum entry count, the log will create a
+     * new segment and append new entries to that segment.
+     * <p>
+     * By default, the maximum entries per segment is {@code 1024 * 1024}.
+     *
+     * @param maxEntriesPerSegment The maximum number of entries allowed per segment.
+     * @return The storage builder.
+     * @throws IllegalArgumentException If the {@code maxEntriesPerSegment} not greater than the default max entries
+     *     per segment
+     * @deprecated since 3.0.2
+     */
+    @Deprecated
+    public Builder<E> withMaxEntriesPerSegment(int maxEntriesPerSegment) {
+      checkArgument(maxEntriesPerSegment > 0, "max entries per segment must be positive");
+      checkArgument(maxEntriesPerSegment <= DEFAULT_MAX_ENTRIES_PER_SEGMENT,
+          "max entries per segment cannot be greater than " + DEFAULT_MAX_ENTRIES_PER_SEGMENT);
+      this.maxEntriesPerSegment = maxEntriesPerSegment;
+      return this;
+    }
+
+    /**
+     * Sets the journal index density.
+     * <p>
+     * The index density is the frequency at which the position of entries written to the journal will be recorded in an
+     * in-memory index for faster seeking.
+     *
+     * @param indexDensity the index density
+     * @return the journal builder
+     * @throws IllegalArgumentException if the density is not between 0 and 1
+     */
+    public Builder<E> withIndexDensity(double indexDensity) {
+      checkArgument(indexDensity > 0 && indexDensity < 1, "index density must be between 0 and 1");
+      this.indexDensity = indexDensity;
+      return this;
+    }
+
+    /**
+     * Sets the journal cache size.
+     *
+     * @param cacheSize the journal cache size
+     * @return the journal builder
+     * @throws IllegalArgumentException if the cache size is not positive
+     * @deprecated since 3.0.4
+     */
+    @Deprecated
+    public Builder<E> withCacheSize(int cacheSize) {
+      checkArgument(cacheSize >= 0, "cacheSize must be positive");
+      this.cacheSize = cacheSize;
+      return this;
+    }
+
+    /**
+     * Enables flushing buffers to disk when entries are committed to a segment, returning the builder for method
+     * chaining.
+     * <p>
+     * When flush-on-commit is enabled, log entry buffers will be automatically flushed to disk each time an entry is
+     * committed in a given segment.
+     *
+     * @return The storage builder.
+     */
+    public Builder<E> withFlushOnCommit() {
+      return withFlushOnCommit(true);
+    }
+
+    /**
+     * Sets whether to flush buffers to disk when entries are committed to a segment, returning the builder for method
+     * chaining.
+     * <p>
+     * When flush-on-commit is enabled, log entry buffers will be automatically flushed to disk each time an entry is
+     * committed in a given segment.
+     *
+     * @param flushOnCommit Whether to flush buffers to disk when entries are committed to a segment.
+     * @return The storage builder.
+     */
+    public Builder<E> withFlushOnCommit(boolean flushOnCommit) {
+      this.flushOnCommit = flushOnCommit;
+      return this;
+    }
+
+    @Override
+    public SegmentedJournal<E> build() {
+      return new SegmentedJournal<>(
+          name,
+          storageLevel,
+          directory,
+          namespace,
+          maxSegmentSize,
+          maxEntrySize,
+          maxEntriesPerSegment,
+          indexDensity,
+          flushOnCommit);
+    }
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/SegmentedJournalReader.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/SegmentedJournalReader.java
new file mode 100644 (file)
index 0000000..799f395
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Raft log reader.
+ */
+public class SegmentedJournalReader<E> implements JournalReader<E> {
+
+  private final SegmentedJournal<E> journal;
+  private JournalSegment<E> currentSegment;
+  private Indexed<E> previousEntry;
+  private MappableJournalSegmentReader<E> currentReader;
+  private final Mode mode;
+
+  public SegmentedJournalReader(SegmentedJournal<E> journal, long index, Mode mode) {
+    this.journal = journal;
+    this.mode = mode;
+    initialize(index);
+  }
+
+  /**
+   * Initializes the reader to the given index.
+   */
+  private void initialize(long index) {
+    currentSegment = journal.getSegment(index);
+    currentSegment.acquire();
+    currentReader = currentSegment.createReader();
+    long nextIndex = getNextIndex();
+    while (index > nextIndex && hasNext()) {
+      next();
+      nextIndex = getNextIndex();
+    }
+  }
+
+  @Override
+  public long getFirstIndex() {
+    return journal.getFirstSegment().index();
+  }
+
+  @Override
+  public long getCurrentIndex() {
+    long currentIndex = currentReader.getCurrentIndex();
+    if (currentIndex != 0) {
+      return currentIndex;
+    }
+    if (previousEntry != null) {
+      return previousEntry.index();
+    }
+    return 0;
+  }
+
+  @Override
+  public Indexed<E> getCurrentEntry() {
+    Indexed<E> currentEntry = currentReader.getCurrentEntry();
+    if (currentEntry != null) {
+      return currentEntry;
+    }
+    return previousEntry;
+  }
+
+  @Override
+  public long getNextIndex() {
+    return currentReader.getNextIndex();
+  }
+
+  @Override
+  public void reset() {
+    currentReader.close();
+    currentSegment.release();
+    currentSegment = journal.getFirstSegment();
+    currentSegment.acquire();
+    currentReader = currentSegment.createReader();
+    previousEntry = null;
+  }
+
+  @Override
+  public void reset(long index) {
+    // If the current segment is not open, it has been replaced. Reset the segments.
+    if (!currentSegment.isOpen()) {
+      reset();
+    }
+
+    if (index < currentReader.getNextIndex()) {
+      rewind(index);
+    } else if (index > currentReader.getNextIndex()) {
+      forward(index);
+    } else {
+      currentReader.reset(index);
+    }
+  }
+
+  /**
+   * Rewinds the journal to the given index.
+   */
+  private void rewind(long index) {
+    if (currentSegment.index() >= index) {
+      JournalSegment<E> segment = journal.getSegment(index - 1);
+      if (segment != null) {
+        currentReader.close();
+        currentSegment.release();
+        currentSegment = segment;
+        currentSegment.acquire();
+        currentReader = currentSegment.createReader();
+      }
+    }
+
+    currentReader.reset(index);
+    previousEntry = currentReader.getCurrentEntry();
+  }
+
+  /**
+   * Fast forwards the journal to the given index.
+   */
+  private void forward(long index) {
+    while (getNextIndex() < index && hasNext()) {
+      next();
+    }
+  }
+
+  @Override
+  public boolean hasNext() {
+    if (mode == Mode.ALL) {
+      return hasNextEntry();
+    }
+
+    long nextIndex = getNextIndex();
+    long commitIndex = journal.getCommitIndex();
+    return nextIndex <= commitIndex && hasNextEntry();
+  }
+
+  private boolean hasNextEntry() {
+    if (!currentReader.hasNext()) {
+      JournalSegment<E> nextSegment = journal.getNextSegment(currentSegment.index());
+      if (nextSegment != null && nextSegment.index() == getNextIndex()) {
+        previousEntry = currentReader.getCurrentEntry();
+        currentSegment.release();
+        currentSegment = nextSegment;
+        currentSegment.acquire();
+        currentReader = currentSegment.createReader();
+        return currentReader.hasNext();
+      }
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  public Indexed<E> next() {
+    if (!currentReader.hasNext()) {
+      JournalSegment<E> nextSegment = journal.getNextSegment(currentSegment.index());
+      if (nextSegment != null && nextSegment.index() == getNextIndex()) {
+        previousEntry = currentReader.getCurrentEntry();
+        currentSegment.release();
+        currentSegment = nextSegment;
+        currentSegment.acquire();
+        currentReader = currentSegment.createReader();
+        return currentReader.next();
+      } else {
+        throw new NoSuchElementException();
+      }
+    } else {
+      previousEntry = currentReader.getCurrentEntry();
+      return currentReader.next();
+    }
+  }
+
+  @Override
+  public void close() {
+    currentReader.close();
+    journal.closeReader(this);
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/SegmentedJournalWriter.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/SegmentedJournalWriter.java
new file mode 100644 (file)
index 0000000..d4a1a68
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import java.nio.BufferOverflowException;
+
+/**
+ * Raft log writer.
+ */
+public class SegmentedJournalWriter<E> implements JournalWriter<E> {
+  private final SegmentedJournal<E> journal;
+  private JournalSegment<E> currentSegment;
+  private MappableJournalSegmentWriter<E> currentWriter;
+
+  public SegmentedJournalWriter(SegmentedJournal<E> journal) {
+    this.journal = journal;
+    this.currentSegment = journal.getLastSegment();
+    currentSegment.acquire();
+    this.currentWriter = currentSegment.writer();
+  }
+
+  @Override
+  public long getLastIndex() {
+    return currentWriter.getLastIndex();
+  }
+
+  @Override
+  public Indexed<E> getLastEntry() {
+    return currentWriter.getLastEntry();
+  }
+
+  @Override
+  public long getNextIndex() {
+    return currentWriter.getNextIndex();
+  }
+
+  @Override
+  public void reset(long index) {
+    if (index > currentSegment.index()) {
+      currentSegment.release();
+      currentSegment = journal.resetSegments(index);
+      currentSegment.acquire();
+      currentWriter = currentSegment.writer();
+    } else {
+      truncate(index - 1);
+    }
+    journal.resetHead(index);
+  }
+
+  @Override
+  public void commit(long index) {
+    if (index > journal.getCommitIndex()) {
+      journal.setCommitIndex(index);
+      if (journal.isFlushOnCommit()) {
+        flush();
+      }
+    }
+  }
+
+  @Override
+  public <T extends E> Indexed<T> append(T entry) {
+    try {
+      return currentWriter.append(entry);
+    } catch (BufferOverflowException e) {
+      if (currentSegment.index() == currentWriter.getNextIndex()) {
+        throw e;
+      }
+      currentWriter.flush();
+      currentSegment.release();
+      currentSegment = journal.getNextSegment();
+      currentSegment.acquire();
+      currentWriter = currentSegment.writer();
+      return currentWriter.append(entry);
+    }
+  }
+
+  @Override
+  public void append(Indexed<E> entry) {
+    try {
+      currentWriter.append(entry);
+    } catch (BufferOverflowException e) {
+      if (currentSegment.index() == currentWriter.getNextIndex()) {
+        throw e;
+      }
+      currentWriter.flush();
+      currentSegment.release();
+      currentSegment = journal.getNextSegment();
+      currentSegment.acquire();
+      currentWriter = currentSegment.writer();
+      currentWriter.append(entry);
+    }
+  }
+
+  @Override
+  public void truncate(long index) {
+    if (index < journal.getCommitIndex()) {
+      throw new IndexOutOfBoundsException("Cannot truncate committed index: " + index);
+    }
+
+    // Delete all segments with first indexes greater than the given index.
+    while (index < currentSegment.index() && currentSegment != journal.getFirstSegment()) {
+      currentSegment.release();
+      journal.removeSegment(currentSegment);
+      currentSegment = journal.getLastSegment();
+      currentSegment.acquire();
+      currentWriter = currentSegment.writer();
+    }
+
+    // Truncate the current index.
+    currentWriter.truncate(index);
+
+    // Reset segment readers.
+    journal.resetTail(index + 1);
+  }
+
+  @Override
+  public void flush() {
+    currentWriter.flush();
+  }
+
+  @Override
+  public void close() {
+    currentWriter.close();
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/JournalIndex.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/JournalIndex.java
new file mode 100644 (file)
index 0000000..baff986
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal.index;
+
+/**
+ * Journal index.
+ */
+public interface JournalIndex {
+
+  /**
+   * Adds an entry for the given index at the given position.
+   *
+   * @param index the index for which to add the entry
+   * @param position the position of the given index
+   */
+  void index(long index, int position);
+
+  /**
+   * Looks up the position of the given index.
+   *
+   * @param index the index to lookup
+   * @return the position of the given index or a lesser index
+   */
+  Position lookup(long index);
+
+  /**
+   * Truncates the index to the given index.
+   *
+   * @param index the index to which to truncate the index
+   */
+  void truncate(long index);
+
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/Position.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/Position.java
new file mode 100644 (file)
index 0000000..669263a
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal.index;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Journal index position.
+ */
+public class Position {
+  private final long index;
+  private final int position;
+
+  public Position(long index, int position) {
+    this.index = index;
+    this.position = position;
+  }
+
+  public long index() {
+    return index;
+  }
+
+  public int position() {
+    return position;
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("index", index)
+        .add("position", position)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/SparseJournalIndex.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/SparseJournalIndex.java
new file mode 100644 (file)
index 0000000..d4b65c8
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal.index;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Sparse index.
+ */
+public class SparseJournalIndex implements JournalIndex {
+  private static final int MIN_DENSITY = 1000;
+  private final int density;
+  private final TreeMap<Long, Integer> positions = new TreeMap<>();
+
+  public SparseJournalIndex(double density) {
+    this.density = (int) Math.ceil(MIN_DENSITY / (density * MIN_DENSITY));
+  }
+
+  @Override
+  public void index(long index, int position) {
+    if (index % density == 0) {
+      positions.put(index, position);
+    }
+  }
+
+  @Override
+  public Position lookup(long index) {
+    Map.Entry<Long, Integer> entry = positions.floorEntry(index);
+    return entry != null ? new Position(entry.getKey(), entry.getValue()) : null;
+  }
+
+  @Override
+  public void truncate(long index) {
+    positions.tailMap(index, false).clear();
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/package-info.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/index/package-info.java
new file mode 100644 (file)
index 0000000..c54e08e
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides classes and interfaces for efficiently managing journal indexes.
+ */
+package io.atomix.storage.journal.index;
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/package-info.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/journal/package-info.java
new file mode 100644 (file)
index 0000000..c2191e1
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides a low-level journal abstraction for appending to logs and managing segmented logs.
+ */
+package io.atomix.storage.journal;
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/package-info.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/package-info.java
new file mode 100644 (file)
index 0000000..0704f64
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides classes and interfaces for managing storage objects.
+ */
+package io.atomix.storage;
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/statistics/StorageStatistics.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/statistics/StorageStatistics.java
new file mode 100644 (file)
index 0000000..cdc910f
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.statistics;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+/**
+ * Atomix storage statistics.
+ */
+public class StorageStatistics {
+  private static final Logger LOGGER = LoggerFactory.getLogger(StorageStatistics.class);
+
+  private final File file;
+  private final MBeanServer mBeanServer;
+
+  public StorageStatistics(File file) {
+    this.file = file;
+    this.mBeanServer = ManagementFactory.getPlatformMBeanServer();
+  }
+
+  /**
+   * Returns the amount of usable space remaining.
+   *
+   * @return the amount of usable space remaining
+   */
+  public long getUsableSpace() {
+    return file.getUsableSpace();
+  }
+
+  /**
+   * Returns the amount of free space remaining.
+   *
+   * @return the amount of free space remaining
+   */
+  public long getFreeSpace() {
+    return file.getFreeSpace();
+  }
+
+  /**
+   * Returns the total amount of space.
+   *
+   * @return the total amount of space
+   */
+  public long getTotalSpace() {
+    return file.getTotalSpace();
+  }
+
+  /**
+   * Returns the amount of free memory remaining.
+   *
+   * @return the amount of free memory remaining if successful, -1 return indicates failure.
+   */
+  public long getFreeMemory() {
+    try {
+      return (long) mBeanServer.getAttribute(new ObjectName("java.lang", "type", "OperatingSystem"), "FreePhysicalMemorySize");
+    } catch (Exception e) {
+      if (LOGGER.isDebugEnabled()) {
+        LOGGER.debug("An exception occurred during memory check", e);
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Returns the total amount of memory.
+   *
+   * @return the total amount of memory if successful, -1 return indicates failure.
+   */
+  public long getTotalMemory() {
+    try {
+      return (long) mBeanServer.getAttribute(new ObjectName("java.lang", "type", "OperatingSystem"), "TotalPhysicalMemorySize");
+    } catch (Exception e) {
+      if (LOGGER.isDebugEnabled()) {
+        LOGGER.debug("An exception occurred during memory check", e);
+      }
+    }
+    return -1;
+  }
+}
diff --git a/third-party/atomix/storage/src/main/java/io/atomix/storage/statistics/package-info.java b/third-party/atomix/storage/src/main/java/io/atomix/storage/statistics/package-info.java
new file mode 100644 (file)
index 0000000..3e046d8
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides utilities for querying system storage information.
+ */
+package io.atomix.storage.statistics;
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/BufferTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/BufferTest.java
new file mode 100644 (file)
index 0000000..cfa025b
--- /dev/null
@@ -0,0 +1,674 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import org.junit.Test;
+
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteOrder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Base buffer test.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public abstract class BufferTest {
+
+  /**
+   * Creates a new test buffer.
+   */
+  protected abstract Buffer createBuffer(int capacity);
+
+  /**
+   * Creates a new test buffer.
+   */
+  protected abstract Buffer createBuffer(int capacity, int maxCapacity);
+
+  @Test
+  public void testPosition() {
+    Buffer buffer = createBuffer(8);
+    assertEquals(0, buffer.position());
+    buffer.writeInt(10);
+    assertEquals(4, buffer.position());
+    buffer.position(0);
+    assertEquals(0, buffer.position());
+    assertEquals(10, buffer.readInt());
+  }
+
+  @Test
+  public void testFlip() {
+    Buffer buffer = createBuffer(8);
+    buffer.writeInt(10);
+    assertEquals(4, buffer.position());
+    assertEquals(8, buffer.capacity());
+    assertEquals(-1, buffer.limit());
+    assertEquals(8, buffer.capacity());
+    buffer.flip();
+    assertEquals(4, buffer.limit());
+    assertEquals(0, buffer.position());
+  }
+
+  @Test
+  public void testLimit() {
+    Buffer buffer = createBuffer(8);
+    assertEquals(0, buffer.position());
+    assertEquals(-1, buffer.limit());
+    assertEquals(8, buffer.capacity());
+    buffer.limit(4);
+    assertEquals(4, buffer.limit());
+    assertTrue(buffer.hasRemaining());
+    buffer.writeInt(10);
+    assertEquals(0, buffer.remaining());
+    assertFalse(buffer.hasRemaining());
+  }
+
+  @Test
+  public void testClear() {
+    Buffer buffer = createBuffer(8);
+    buffer.limit(6);
+    assertEquals(6, buffer.limit());
+    buffer.writeInt(10);
+    assertEquals(4, buffer.position());
+    buffer.clear();
+    assertEquals(-1, buffer.limit());
+    assertEquals(8, buffer.capacity());
+    assertEquals(0, buffer.position());
+  }
+
+  @Test
+  public void testMarkReset() {
+    assertTrue(createBuffer(12).writeInt(10).mark().writeBoolean(true).reset().readBoolean());
+  }
+
+  @Test(expected = BufferUnderflowException.class)
+  public void testReadIntThrowsBufferUnderflowWithNoRemainingBytesRelative() {
+    createBuffer(4, 4)
+      .writeInt(10)
+      .readInt();
+  }
+
+  @Test(expected = BufferUnderflowException.class)
+  public void testReadIntThrowsBufferUnderflowWithNoRemainingBytesAbsolute() {
+    createBuffer(4, 4).readInt(2);
+  }
+
+  @Test(expected = BufferOverflowException.class)
+  public void testWriteIntThrowsBufferOverflowWithNoRemainingBytesRelative() {
+    createBuffer(4, 4).writeInt(10).writeInt(20);
+  }
+
+  @Test(expected = BufferOverflowException.class)
+  public void testReadIntThrowsBufferOverflowWithNoRemainingBytesAbsolute() {
+    createBuffer(4, 4).writeInt(4, 10);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testReadIntThrowsIndexOutOfBounds() {
+    createBuffer(4, 4).readInt(10);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testWriteIntThrowsIndexOutOfBounds() {
+    createBuffer(4, 4).writeInt(10, 10);
+  }
+
+  @Test
+  public void testWriteReadByteRelative() {
+    assertEquals(10, createBuffer(16).writeByte(10).flip().readByte());
+  }
+
+  @Test
+  public void testWriteReadByteAbsolute() {
+    assertEquals(10, createBuffer(16).writeByte(4, 10).readByte(4));
+  }
+
+  @Test
+  public void testWriteReadUnsignedByteRelative() {
+    assertEquals(10, createBuffer(16).writeUnsignedByte(10).flip().readUnsignedByte());
+  }
+
+  @Test
+  public void testWriteReadUnsignedByteAbsolute() {
+    assertEquals(10, createBuffer(16).writeUnsignedByte(4, 10).readUnsignedByte(4));
+  }
+
+  @Test
+  public void testWriteReadShortRelative() {
+    assertEquals(10, createBuffer(16).writeShort((short) 10).flip().readShort());
+  }
+
+  @Test
+  public void testWriteReadShortAbsolute() {
+    assertEquals(10, createBuffer(16).writeShort(4, (short) 10).readShort(4));
+  }
+
+  @Test
+  public void testWriteReadUnsignedShortRelative() {
+    assertEquals(10, createBuffer(16).writeUnsignedShort((short) 10).flip().readUnsignedShort());
+  }
+
+  @Test
+  public void testWriteReadUnsignedShortAbsolute() {
+    assertEquals(10, createBuffer(16).writeUnsignedShort(4, (short) 10).readUnsignedShort(4));
+  }
+
+  @Test
+  public void testWriteReadIntRelative() {
+    assertEquals(10, createBuffer(16).writeInt(10).flip().readInt());
+  }
+
+  @Test
+  public void testWriteReadUnsignedIntAbsolute() {
+    assertEquals(10, createBuffer(16).writeUnsignedInt(4, 10).readUnsignedInt(4));
+  }
+
+  @Test
+  public void testWriteReadUnsignedIntRelative() {
+    assertEquals(10, createBuffer(16).writeUnsignedInt(10).flip().readUnsignedInt());
+  }
+
+  @Test
+  public void testWriteReadIntAbsolute() {
+    assertEquals(10, createBuffer(16).writeInt(4, 10).readInt(4));
+  }
+
+  @Test
+  public void testWriteReadLongRelative() {
+    assertEquals(12345, createBuffer(16).writeLong(12345).flip().readLong());
+  }
+
+  @Test
+  public void testWriteReadLongAbsolute() {
+    assertEquals(12345, createBuffer(16).writeLong(4, 12345).readLong(4));
+  }
+
+  @Test
+  public void testWriteReadFloatRelative() {
+    assertEquals(10.6f, createBuffer(16).writeFloat(10.6f).flip().readFloat(), .001);
+  }
+
+  @Test
+  public void testWriteReadFloatAbsolute() {
+    assertEquals(10.6f, createBuffer(16).writeFloat(4, 10.6f).readFloat(4), .001);
+  }
+
+  @Test
+  public void testWriteReadDoubleRelative() {
+    assertEquals(10.6, createBuffer(16).writeDouble(10.6).flip().readDouble(), .001);
+  }
+
+  @Test
+  public void testWriteReadDoubleAbsolute() {
+    assertEquals(10.6, createBuffer(16).writeDouble(4, 10.6).readDouble(4), .001);
+  }
+
+  @Test
+  public void testWriteReadBooleanRelative() {
+    assertTrue(createBuffer(16).writeBoolean(true).flip().readBoolean());
+  }
+
+  @Test
+  public void testWriteReadBooleanAbsolute() {
+    assertTrue(createBuffer(16).writeBoolean(4, true).readBoolean(4));
+  }
+
+  @Test
+  public void testWriteReadStringRelative() {
+    Buffer buffer = createBuffer(38)
+        .writeString("Hello world!")
+        .writeString("Hello world again!")
+        .flip();
+    assertEquals("Hello world!", buffer.readString());
+    assertEquals("Hello world again!", buffer.readString());
+  }
+
+  @Test
+  public void testWriteReadStringAbsolute() {
+    Buffer buffer = createBuffer(46)
+        .writeString(4, "Hello world!")
+        .writeString(20, "Hello world again!");
+    assertEquals("Hello world!", buffer.readString(4));
+    assertEquals("Hello world again!", buffer.readString(20));
+  }
+
+  @Test
+  public void testWriteReadUTF8Relative() {
+    Buffer buffer = createBuffer(38)
+        .writeUTF8("Hello world!")
+        .writeUTF8("Hello world again!")
+        .flip();
+    assertEquals("Hello world!", buffer.readUTF8());
+    assertEquals("Hello world again!", buffer.readUTF8());
+  }
+
+  @Test
+  public void testWriteReadUTF8Absolute() {
+    Buffer buffer = createBuffer(46)
+        .writeUTF8(4, "Hello world!")
+        .writeUTF8(20, "Hello world again!");
+    assertEquals("Hello world!", buffer.readUTF8(4));
+    assertEquals("Hello world again!", buffer.readUTF8(20));
+  }
+
+  @Test
+  public void testReadWriter() {
+    Buffer writeBuffer = createBuffer(8).writeLong(10).flip();
+    Buffer readBuffer = createBuffer(8);
+    writeBuffer.read(readBuffer);
+    assertEquals(10, readBuffer.flip().readLong());
+  }
+
+  @Test
+  public void testWriteReadSwappedIntRelative() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeInt(10).flip().readInt());
+  }
+
+  @Test
+  public void testWriteReadSwappedIntAbsolute() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeInt(4, 10).readInt(4));
+  }
+
+  @Test
+  public void testAbsoluteSlice() {
+    Buffer buffer = createBuffer(1024);
+    buffer.writeLong(10).writeLong(11).rewind();
+    Buffer slice = buffer.slice(8, 1016);
+    assertEquals(0, slice.position());
+    assertEquals(11, slice.readLong());
+  }
+
+  @Test
+  public void testRelativeSliceWithoutLength() {
+    Buffer buffer = createBuffer(1024, 1024);
+    buffer.writeLong(10).writeLong(11).writeLong(12).rewind();
+    assertEquals(10, buffer.readLong());
+    Buffer slice = buffer.slice();
+    assertEquals(0, slice.position());
+    assertEquals(-1, slice.limit());
+    assertEquals(1016, slice.capacity());
+    assertEquals(1016, slice.maxCapacity());
+    assertEquals(11, slice.readLong());
+    assertEquals(11, slice.readLong(0));
+    slice.close();
+    Buffer slice2 = buffer.skip(8).slice();
+    assertEquals(0, slice2.position());
+    assertEquals(-1, slice2.limit());
+    assertEquals(1008, slice2.capacity());
+    assertEquals(1008, slice2.maxCapacity());
+    assertEquals(12, slice2.readLong());
+    assertEquals(12, slice2.readLong(0));
+  }
+
+  @Test
+  public void testRelativeSliceWithLength() {
+    Buffer buffer = createBuffer(1024);
+    buffer.writeLong(10).writeLong(11).writeLong(12).rewind();
+    assertEquals(10, buffer.readLong());
+    Buffer slice = buffer.slice(8);
+    assertEquals(0, slice.position());
+    assertEquals(11, slice.readLong());
+    assertEquals(11, slice.readLong(0));
+    slice.close();
+    Buffer slice2 = buffer.skip(8).slice(8);
+    assertEquals(0, slice2.position());
+    assertEquals(12, slice2.readLong());
+    assertEquals(12, slice2.readLong(0));
+    slice2.close();
+  }
+
+  @Test
+  public void testSliceOfSlice() {
+    Buffer buffer = createBuffer(1024);
+    buffer.writeLong(10).writeLong(11).writeLong(12).rewind();
+    assertEquals(10, buffer.readLong());
+    Buffer slice = buffer.slice();
+    assertEquals(11, slice.readLong());
+    Buffer sliceOfSlice = slice.slice();
+    assertEquals(12, sliceOfSlice.readLong());
+    assertEquals(8, sliceOfSlice.position());
+  }
+
+  @Test
+  public void testSliceWithLimit() {
+    Buffer buffer = createBuffer(1024).limit(16);
+    buffer.writeLong(10);
+    Buffer slice = buffer.slice();
+    assertEquals(0, slice.position());
+    assertEquals(8, slice.capacity());
+    assertEquals(8, slice.maxCapacity());
+    assertEquals(8, slice.remaining());
+  }
+
+  @Test
+  public void testSliceWithLittleRemaining() {
+    Buffer buffer = createBuffer(1024, 2048);
+    buffer.position(1020);
+    Buffer slice = buffer.slice(8);
+    assertEquals(0, slice.position());
+    assertEquals(-1, slice.limit());
+  }
+
+  @Test
+  public void testCompact() {
+    Buffer buffer = createBuffer(1024);
+    buffer.position(100).writeLong(1234).position(100).compact();
+    assertEquals(0, buffer.position());
+    assertEquals(1234, buffer.readLong());
+  }
+
+  @Test
+  public void testSwappedPosition() {
+    Buffer buffer = createBuffer(8).order(ByteOrder.LITTLE_ENDIAN);
+    assertEquals(0, buffer.position());
+    buffer.writeInt(10);
+    assertEquals(4, buffer.position());
+    buffer.position(0);
+    assertEquals(0, buffer.position());
+    assertEquals(10, buffer.readInt());
+  }
+
+  @Test
+  public void testSwappedFlip() {
+    Buffer buffer = createBuffer(8).order(ByteOrder.LITTLE_ENDIAN);
+    buffer.writeInt(10);
+    assertEquals(4, buffer.position());
+    assertEquals(8, buffer.capacity());
+    assertEquals(-1, buffer.limit());
+    assertEquals(8, buffer.capacity());
+    buffer.flip();
+    assertEquals(4, buffer.limit());
+    assertEquals(0, buffer.position());
+  }
+
+  @Test
+  public void testSwappedLimit() {
+    Buffer buffer = createBuffer(8).order(ByteOrder.LITTLE_ENDIAN);
+    assertEquals(0, buffer.position());
+    assertEquals(-1, buffer.limit());
+    assertEquals(8, buffer.capacity());
+    buffer.limit(4);
+    assertEquals(4, buffer.limit());
+    assertTrue(buffer.hasRemaining());
+    buffer.writeInt(10);
+    assertEquals(0, buffer.remaining());
+    assertFalse(buffer.hasRemaining());
+  }
+
+  @Test
+  public void testSwappedClear() {
+    Buffer buffer = createBuffer(8).order(ByteOrder.LITTLE_ENDIAN);
+    buffer.limit(6);
+    assertEquals(6, buffer.limit());
+    buffer.writeInt(10);
+    assertEquals(4, buffer.position());
+    buffer.clear();
+    assertEquals(-1, buffer.limit());
+    assertEquals(8, buffer.capacity());
+    assertEquals(0, buffer.position());
+  }
+
+  @Test
+  public void testSwappedMarkReset() {
+    assertTrue(createBuffer(12).order(ByteOrder.LITTLE_ENDIAN).writeInt(10).mark().writeBoolean(true).reset().readBoolean());
+  }
+
+  @Test(expected = BufferUnderflowException.class)
+  public void testSwappedReadIntThrowsBufferUnderflowWithNoRemainingBytesRelative() {
+    createBuffer(4, 4).order(ByteOrder.LITTLE_ENDIAN)
+      .writeInt(10)
+      .readInt();
+  }
+
+  @Test(expected = BufferUnderflowException.class)
+  public void testSwappedReadIntThrowsBufferUnderflowWithNoRemainingBytesAbsolute() {
+    createBuffer(4, 4).order(ByteOrder.LITTLE_ENDIAN).readInt(2);
+  }
+
+  @Test(expected = BufferOverflowException.class)
+  public void testSwappedWriteIntThrowsBufferOverflowWithNoRemainingBytesRelative() {
+    createBuffer(4, 4).order(ByteOrder.LITTLE_ENDIAN).writeInt(10).writeInt(20);
+  }
+
+  @Test(expected = BufferOverflowException.class)
+  public void testSwappedReadIntThrowsBufferOverflowWithNoRemainingBytesAbsolute() {
+    createBuffer(4, 4).order(ByteOrder.LITTLE_ENDIAN).writeInt(4, 10);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testSwappedReadIntThrowsIndexOutOfBounds() {
+    createBuffer(4, 4).order(ByteOrder.LITTLE_ENDIAN).readInt(10);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testSwappedWriteIntThrowsIndexOutOfBounds() {
+    createBuffer(4, 4).order(ByteOrder.LITTLE_ENDIAN).writeInt(10, 10);
+  }
+
+  @Test
+  public void testSwappedWriteReadByteRelative() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeByte(10).flip().readByte());
+  }
+
+  @Test
+  public void testSwappedWriteReadByteAbsolute() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeByte(4, 10).readByte(4));
+  }
+
+  @Test
+  public void testSwappedWriteReadUnsignedByteRelative() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeUnsignedByte(10).flip().readUnsignedByte());
+  }
+
+  @Test
+  public void testSwappedWriteReadUnsignedByteAbsolute() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeUnsignedByte(4, 10).readUnsignedByte(4));
+  }
+
+  @Test
+  public void testSwappedWriteReadShortRelative() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeShort((short) 10).flip().readShort());
+  }
+
+  @Test
+  public void testSwappedWriteReadShortAbsolute() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeShort(4, (short) 10).readShort(4));
+  }
+
+  @Test
+  public void testSwappedWriteReadUnsignedShortRelative() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeUnsignedShort((short) 10).flip().readUnsignedShort());
+  }
+
+  @Test
+  public void testSwappedWriteReadUnsignedShortAbsolute() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeUnsignedShort(4, (short) 10).readUnsignedShort(4));
+  }
+
+  @Test
+  public void testSwappedWriteReadIntRelative() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeInt(10).flip().readInt());
+  }
+
+  @Test
+  public void testSwappedWriteReadUnsignedIntAbsolute() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeUnsignedInt(4, 10).readUnsignedInt(4));
+  }
+
+  @Test
+  public void testSwappedWriteReadUnsignedIntRelative() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeUnsignedInt(10).flip().readUnsignedInt());
+  }
+
+  @Test
+  public void testSwappedWriteReadIntAbsolute() {
+    assertEquals(10, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeInt(4, 10).readInt(4));
+  }
+
+  @Test
+  public void testSwappedWriteReadLongRelative() {
+    assertEquals(12345, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeLong(12345).flip().readLong());
+  }
+
+  @Test
+  public void testSwappedWriteReadLongAbsolute() {
+    assertEquals(12345, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeLong(4, 12345).readLong(4));
+  }
+
+  @Test
+  public void testSwappedWriteReadFloatRelative() {
+    assertEquals(10.6f, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeFloat(10.6f).flip().readFloat(), .001);
+  }
+
+  @Test
+  public void testSwappedWriteReadFloatAbsolute() {
+    assertEquals(10.6f, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeFloat(4, 10.6f).readFloat(4), .001);
+  }
+
+  @Test
+  public void testSwappedWriteReadDoubleRelative() {
+    assertEquals(10.6, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeDouble(10.6).flip().readDouble(), .001);
+  }
+
+  @Test
+  public void testSwappedWriteReadDoubleAbsolute() {
+    assertEquals(10.6, createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeDouble(4, 10.6).readDouble(4), .001);
+  }
+
+  @Test
+  public void testSwappedWriteReadBooleanRelative() {
+    assertTrue(createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeBoolean(true).flip().readBoolean());
+  }
+
+  @Test
+  public void testSwappedWriteReadBooleanAbsolute() {
+    assertTrue(createBuffer(16).order(ByteOrder.LITTLE_ENDIAN).writeBoolean(4, true).readBoolean(4));
+  }
+
+  @Test
+  public void testSwappedReadWriter() {
+    Buffer writeBuffer = createBuffer(8).order(ByteOrder.LITTLE_ENDIAN).writeLong(10).flip();
+    Buffer readBuffer = createBuffer(8).order(ByteOrder.LITTLE_ENDIAN);
+    writeBuffer.read(readBuffer);
+    assertEquals(10, readBuffer.flip().readLong());
+  }
+
+  @Test
+  public void testSwappedAbsoluteSlice() {
+    Buffer buffer = createBuffer(1024).order(ByteOrder.LITTLE_ENDIAN);
+    buffer.writeLong(10).writeLong(11).rewind();
+    Buffer slice = buffer.slice(8, 1016);
+    assertEquals(0, slice.position());
+    assertEquals(11, slice.readLong());
+  }
+
+  @Test
+  public void testSwappedRelativeSliceWithoutLength() {
+    Buffer buffer = createBuffer(1024, 1024).order(ByteOrder.LITTLE_ENDIAN);
+    buffer.writeLong(10).writeLong(11).writeLong(12).rewind();
+    assertEquals(10, buffer.readLong());
+    Buffer slice = buffer.slice();
+    assertEquals(0, slice.position());
+    assertEquals(-1, slice.limit());
+    assertEquals(1016, slice.capacity());
+    assertEquals(1016, slice.maxCapacity());
+    assertEquals(11, slice.readLong());
+    assertEquals(11, slice.readLong(0));
+    slice.close();
+    Buffer slice2 = buffer.skip(8).slice();
+    assertEquals(0, slice2.position());
+    assertEquals(-1, slice2.limit());
+    assertEquals(1008, slice2.capacity());
+    assertEquals(1008, slice2.maxCapacity());
+    assertEquals(12, slice2.readLong());
+    assertEquals(12, slice2.readLong(0));
+  }
+
+  @Test
+  public void testSwappedRelativeSliceWithLength() {
+    Buffer buffer = createBuffer(1024).order(ByteOrder.LITTLE_ENDIAN);
+    buffer.writeLong(10).writeLong(11).writeLong(12).rewind();
+    assertEquals(10, buffer.readLong());
+    Buffer slice = buffer.slice(8);
+    assertEquals(0, slice.position());
+    assertEquals(11, slice.readLong());
+    assertEquals(11, slice.readLong(0));
+    slice.close();
+    Buffer slice2 = buffer.skip(8).slice(8);
+    assertEquals(0, slice2.position());
+    assertEquals(12, slice2.readLong());
+    assertEquals(12, slice2.readLong(0));
+    slice2.close();
+  }
+
+  @Test
+  public void testSwappedSliceOfSlice() {
+    Buffer buffer = createBuffer(1024).order(ByteOrder.LITTLE_ENDIAN);
+    buffer.writeLong(10).writeLong(11).writeLong(12).rewind();
+    assertEquals(10, buffer.readLong());
+    Buffer slice = buffer.slice();
+    assertEquals(11, slice.readLong());
+    Buffer sliceOfSlice = slice.slice();
+    assertEquals(12, sliceOfSlice.readLong());
+    assertEquals(8, sliceOfSlice.position());
+  }
+
+  @Test
+  public void testSwappedSliceWithLimit() {
+    Buffer buffer = createBuffer(1024).order(ByteOrder.LITTLE_ENDIAN).limit(16);
+    buffer.writeLong(10);
+    Buffer slice = buffer.slice();
+    assertEquals(0, slice.position());
+    assertEquals(8, slice.capacity());
+    assertEquals(8, slice.maxCapacity());
+    assertEquals(8, slice.remaining());
+  }
+
+  @Test
+  public void testSwappedSliceWithLittleRemaining() {
+    Buffer buffer = createBuffer(1024, 2048).order(ByteOrder.LITTLE_ENDIAN);
+    buffer.position(1020);
+    Buffer slice = buffer.slice(8);
+    assertEquals(0, slice.position());
+    assertEquals(-1, slice.limit());
+  }
+
+  @Test
+  public void testSwappedCompact() {
+    Buffer buffer = createBuffer(1024).order(ByteOrder.LITTLE_ENDIAN);
+    buffer.position(100).writeLong(1234).position(100).compact();
+    assertEquals(0, buffer.position());
+    assertEquals(1234, buffer.readLong());
+  }
+
+  @Test
+  public void testCapacity0Read() {
+    Buffer buffer = createBuffer(0, 1024);
+    assertEquals(0, buffer.readLong());
+  }
+
+  @Test
+  public void testCapacity0Write() {
+    Buffer buffer = createBuffer(0, 1024);
+    buffer.writeLong(10);
+    assertEquals(10, buffer.readLong(0));
+  }
+
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/DirectBufferTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/DirectBufferTest.java
new file mode 100644 (file)
index 0000000..5602b5a
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Direct buffer test.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class DirectBufferTest extends BufferTest {
+
+  @Override
+  protected Buffer createBuffer(int capacity) {
+    return DirectBuffer.allocate(capacity);
+  }
+
+  @Override
+  protected Buffer createBuffer(int capacity, int maxCapacity) {
+    return DirectBuffer.allocate(capacity, maxCapacity);
+  }
+
+  @Test
+  public void testByteBufferToDirectBuffer() {
+    ByteBuffer byteBuffer = ByteBuffer.allocate(8);
+    byteBuffer.putLong(10);
+    byteBuffer.flip();
+
+    DirectBuffer directBuffer = DirectBuffer.allocate(8);
+    directBuffer.write(byteBuffer.array());
+    directBuffer.flip();
+    assertEquals(directBuffer.readLong(), byteBuffer.getLong());
+    assertTrue(directBuffer.isDirect());
+    directBuffer.release();
+  }
+
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/FileBufferTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/FileBufferTest.java
new file mode 100644 (file)
index 0000000..9a8262f
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import org.junit.AfterClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.file.Files;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * File buffer test.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class FileBufferTest extends BufferTest {
+  @AfterClass
+  public static void afterTest() {
+    FileTesting.cleanFiles();
+  }
+
+  @Override
+  protected Buffer createBuffer(int capacity) {
+    return FileBuffer.allocate(FileTesting.createFile(), capacity);
+  }
+
+  @Override
+  protected Buffer createBuffer(int capacity, int maxCapacity) {
+    return FileBuffer.allocate(FileTesting.createFile(), capacity, maxCapacity);
+  }
+
+  @Test
+  public void testFileToHeapBuffer() {
+    File file = FileTesting.createFile();
+    try (FileBuffer buffer = FileBuffer.allocate(file, 16)) {
+      buffer.writeLong(10).writeLong(11).flip();
+      byte[] bytes = new byte[16];
+      buffer.read(bytes).rewind();
+      HeapBuffer heapBuffer = HeapBuffer.wrap(bytes);
+      assertEquals(buffer.readLong(), heapBuffer.readLong());
+      assertEquals(buffer.readLong(), heapBuffer.readLong());
+    }
+  }
+
+  /**
+   * Rests reopening a file that has been closed.
+   */
+  @Test
+  public void testPersist() {
+    File file = FileTesting.createFile();
+    try (FileBuffer buffer = FileBuffer.allocate(file, 16)) {
+      buffer.writeLong(10).writeLong(11).flip();
+      assertEquals(10, buffer.readLong());
+      assertEquals(11, buffer.readLong());
+    }
+    try (FileBuffer buffer = FileBuffer.allocate(file, 16)) {
+      assertEquals(10, buffer.readLong());
+      assertEquals(11, buffer.readLong());
+    }
+  }
+
+  /**
+   * Tests deleting a file.
+   */
+  @Test
+  public void testDelete() {
+    File file = FileTesting.createFile();
+    FileBuffer buffer = FileBuffer.allocate(file, 16);
+    buffer.writeLong(10).writeLong(11).flip();
+    assertEquals(10, buffer.readLong());
+    assertEquals(11, buffer.readLong());
+    assertTrue(Files.exists(file.toPath()));
+    buffer.delete();
+    assertFalse(Files.exists(file.toPath()));
+  }
+
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/FileTesting.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/FileTesting.java
new file mode 100644 (file)
index 0000000..fc98088
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import java.io.File;
+import java.io.IOException;
+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.util.UUID;
+
+public abstract class FileTesting {
+  public static File createFile() {
+    File file = new File("target/test-files/" + UUID.randomUUID().toString());
+    file.getParentFile().mkdirs();
+    return file;
+  }
+
+  public static void cleanFiles() {
+    Path directory = Paths.get("target/test-files/");
+    try {
+      Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
+        @Override
+        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+          Files.delete(file);
+          return FileVisitResult.CONTINUE;
+        }
+
+        @Override
+        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+          Files.delete(dir);
+          return FileVisitResult.CONTINUE;
+        }
+      });
+    } catch (Exception ignore) {
+    }
+  }
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/HeapBufferTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/HeapBufferTest.java
new file mode 100644 (file)
index 0000000..9873028
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Heap buffer test.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class HeapBufferTest extends BufferTest {
+
+  @Override
+  protected Buffer createBuffer(int capacity) {
+    return HeapBuffer.allocate(capacity);
+  }
+
+  @Override
+  protected Buffer createBuffer(int capacity, int maxCapacity) {
+    return HeapBuffer.allocate(capacity, maxCapacity);
+  }
+
+  @Test
+  public void testByteBufferToHeapBuffer() {
+    ByteBuffer byteBuffer = ByteBuffer.allocate(8);
+    byteBuffer.putLong(10);
+    byteBuffer.rewind();
+
+    HeapBuffer directBuffer = HeapBuffer.wrap(byteBuffer.array());
+    assertEquals(directBuffer.readLong(), byteBuffer.getLong());
+  }
+
+  @Test
+  public void testDirectToHeapBuffer() {
+    DirectBuffer directBuffer = DirectBuffer.allocate(8);
+    directBuffer.writeLong(10);
+    directBuffer.flip();
+
+    byte[] bytes = new byte[8];
+    directBuffer.read(bytes);
+    directBuffer.rewind();
+
+    HeapBuffer heapBuffer = HeapBuffer.wrap(bytes);
+    assertEquals(directBuffer.readLong(), heapBuffer.readLong());
+
+    directBuffer.release();
+  }
+
+  @Test
+  public void testHeapToDirectBuffer() {
+    HeapBuffer heapBuffer = HeapBuffer.allocate(8);
+    heapBuffer.writeLong(10);
+    heapBuffer.flip();
+
+    DirectBuffer directBuffer = DirectBuffer.allocate(8);
+    directBuffer.write(heapBuffer.array());
+    directBuffer.flip();
+
+    assertEquals(directBuffer.readLong(), heapBuffer.readLong());
+
+    directBuffer.release();
+  }
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/MappedBufferTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/buffer/MappedBufferTest.java
new file mode 100644 (file)
index 0000000..2d9a2b2
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.buffer;
+
+import org.junit.AfterClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.file.Files;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Mapped buffer test.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class MappedBufferTest extends BufferTest {
+  @AfterClass
+  public static void afterTest() {
+    FileTesting.cleanFiles();
+  }
+
+  @Override
+  protected Buffer createBuffer(int capacity) {
+    return MappedBuffer.allocate(FileTesting.createFile(), capacity);
+  }
+
+  @Override
+  protected Buffer createBuffer(int capacity, int maxCapacity) {
+    return MappedBuffer.allocate(FileTesting.createFile(), capacity, maxCapacity);
+  }
+
+  /**
+   * Rests reopening a file that has been closed.
+   */
+  @Test
+  public void testPersist() {
+    File file = FileTesting.createFile();
+    try (MappedBuffer buffer = MappedBuffer.allocate(file, 16)) {
+      buffer.writeLong(10).writeLong(11).flip();
+      assertEquals(10, buffer.readLong());
+      assertEquals(11, buffer.readLong());
+    }
+    try (MappedBuffer buffer = MappedBuffer.allocate(file, 16)) {
+      assertEquals(10, buffer.readLong());
+      assertEquals(11, buffer.readLong());
+    }
+  }
+
+  /**
+   * Tests deleting a file.
+   */
+  @Test
+  public void testDelete() {
+    File file = FileTesting.createFile();
+    MappedBuffer buffer = MappedBuffer.allocate(file, 16);
+    buffer.writeLong(10).writeLong(11).flip();
+    assertEquals(10, buffer.readLong());
+    assertEquals(11, buffer.readLong());
+    assertTrue(Files.exists(file.toPath()));
+    buffer.delete();
+    assertFalse(Files.exists(file.toPath()));
+  }
+
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/AbstractJournalTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/AbstractJournalTest.java
new file mode 100644 (file)
index 0000000..8e027da
--- /dev/null
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import io.atomix.storage.StorageLevel;
+import io.atomix.utils.serializer.Namespace;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+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.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Base journal test.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+@RunWith(Parameterized.class)
+public abstract class AbstractJournalTest {
+  private static final Namespace NAMESPACE = Namespace.builder()
+      .register(TestEntry.class)
+      .register(byte[].class)
+      .build();
+
+  protected static final TestEntry ENTRY = new TestEntry(32);
+  private static final Path PATH = Paths.get("target/test-logs/");
+
+  private final int maxSegmentSize;
+  private final int cacheSize;
+  protected final int entriesPerSegment;
+
+  protected AbstractJournalTest(int maxSegmentSize, int cacheSize) {
+    this.maxSegmentSize = maxSegmentSize;
+    this.cacheSize = cacheSize;
+    int entryLength = (NAMESPACE.serialize(ENTRY).length + 8);
+    this.entriesPerSegment = (maxSegmentSize - 64) / entryLength;
+  }
+
+  protected abstract StorageLevel storageLevel();
+
+  @Parameterized.Parameters
+  public static Collection primeNumbers() {
+    List<Object[]> runs = new ArrayList<>();
+    for (int i = 1; i <= 10; i++) {
+      for (int j = 1; j <= 10; j++) {
+        runs.add(new Object[]{64 + (i * (NAMESPACE.serialize(ENTRY).length + 8) + j), j});
+      }
+    }
+    return runs;
+  }
+
+  protected SegmentedJournal<TestEntry> createJournal() {
+    return SegmentedJournal.<TestEntry>builder()
+        .withName("test")
+        .withDirectory(PATH.toFile())
+        .withNamespace(NAMESPACE)
+        .withStorageLevel(storageLevel())
+        .withMaxSegmentSize(maxSegmentSize)
+        .withIndexDensity(.2)
+        .withCacheSize(cacheSize)
+        .build();
+  }
+
+  @Test
+  public void testCloseMultipleTimes() {
+    // given
+    final Journal<TestEntry> journal = createJournal();
+
+    // when
+    journal.close();
+
+    // then
+    journal.close();
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testWriteRead() throws Exception {
+    try (Journal<TestEntry> journal = createJournal()) {
+      JournalWriter<TestEntry> writer = journal.writer();
+      JournalReader<TestEntry> reader = journal.openReader(1);
+
+      // Append a couple entries.
+      Indexed<TestEntry> indexed;
+      assertEquals(1, writer.getNextIndex());
+      indexed = writer.append(ENTRY);
+      assertEquals(1, indexed.index());
+
+      assertEquals(2, writer.getNextIndex());
+      writer.append(new Indexed<>(2, ENTRY, 0));
+      reader.reset(2);
+      indexed = reader.next();
+      assertEquals(2, indexed.index());
+      assertFalse(reader.hasNext());
+
+      // Test reading an entry
+      Indexed<TestEntry> entry1;
+      reader.reset();
+      entry1 = (Indexed) reader.next();
+      assertEquals(1, entry1.index());
+      assertEquals(entry1, reader.getCurrentEntry());
+      assertEquals(1, reader.getCurrentIndex());
+
+      // Test reading a second entry
+      Indexed<TestEntry> entry2;
+      assertTrue(reader.hasNext());
+      assertEquals(2, reader.getNextIndex());
+      entry2 = (Indexed) reader.next();
+      assertEquals(2, entry2.index());
+      assertEquals(entry2, reader.getCurrentEntry());
+      assertEquals(2, reader.getCurrentIndex());
+      assertFalse(reader.hasNext());
+
+      // Test opening a new reader and reading from the journal.
+      reader = journal.openReader(1);
+      assertTrue(reader.hasNext());
+      entry1 = (Indexed) reader.next();
+      assertEquals(1, entry1.index());
+      assertEquals(entry1, reader.getCurrentEntry());
+      assertEquals(1, reader.getCurrentIndex());
+      assertTrue(reader.hasNext());
+
+      assertTrue(reader.hasNext());
+      assertEquals(2, reader.getNextIndex());
+      entry2 = (Indexed) reader.next();
+      assertEquals(2, entry2.index());
+      assertEquals(entry2, reader.getCurrentEntry());
+      assertEquals(2, reader.getCurrentIndex());
+      assertFalse(reader.hasNext());
+
+      // Reset the reader.
+      reader.reset();
+
+      // Test opening a new reader and reading from the journal.
+      reader = journal.openReader(1);
+      assertTrue(reader.hasNext());
+      entry1 = (Indexed) reader.next();
+      assertEquals(1, entry1.index());
+      assertEquals(entry1, reader.getCurrentEntry());
+      assertEquals(1, reader.getCurrentIndex());
+      assertTrue(reader.hasNext());
+
+      assertTrue(reader.hasNext());
+      assertEquals(2, reader.getNextIndex());
+      entry2 = (Indexed) reader.next();
+      assertEquals(2, entry2.index());
+      assertEquals(entry2, reader.getCurrentEntry());
+      assertEquals(2, reader.getCurrentIndex());
+      assertFalse(reader.hasNext());
+
+      // Truncate the journal and write a different entry.
+      writer.truncate(1);
+      assertEquals(2, writer.getNextIndex());
+      writer.append(new Indexed<>(2, ENTRY, 0));
+      reader.reset(2);
+      indexed = reader.next();
+      assertEquals(2, indexed.index());
+
+      // Reset the reader to a specific index and read the last entry again.
+      reader.reset(2);
+
+      assertNotNull(reader.getCurrentEntry());
+      assertEquals(1, reader.getCurrentIndex());
+      assertEquals(1, reader.getCurrentEntry().index());
+      assertTrue(reader.hasNext());
+      assertEquals(2, reader.getNextIndex());
+      entry2 = (Indexed) reader.next();
+      assertEquals(2, entry2.index());
+      assertEquals(entry2, reader.getCurrentEntry());
+      assertEquals(2, reader.getCurrentIndex());
+      assertFalse(reader.hasNext());
+    }
+  }
+
+  @Test
+  public void testResetTruncateZero() throws Exception {
+    try (SegmentedJournal<TestEntry> journal = createJournal()) {
+      JournalWriter<TestEntry> writer = journal.writer();
+      JournalReader<TestEntry> reader = journal.openReader(1);
+
+      assertEquals(0, writer.getLastIndex());
+      writer.append(ENTRY);
+      writer.append(ENTRY);
+      writer.reset(1);
+      assertEquals(0, writer.getLastIndex());
+      writer.append(ENTRY);
+      assertEquals(1, reader.next().index());
+      writer.reset(1);
+      assertEquals(0, writer.getLastIndex());
+      writer.append(ENTRY);
+      assertEquals(1, writer.getLastIndex());
+      assertEquals(1, writer.getLastEntry().index());
+
+      assertTrue(reader.hasNext());
+      assertEquals(1, reader.next().index());
+
+      writer.truncate(0);
+      assertEquals(0, writer.getLastIndex());
+      assertNull(writer.getLastEntry());
+      writer.append(ENTRY);
+      assertEquals(1, writer.getLastIndex());
+      assertEquals(1, writer.getLastEntry().index());
+
+      assertTrue(reader.hasNext());
+      assertEquals(1, reader.next().index());
+    }
+  }
+
+  @Test
+  public void testTruncateRead() throws Exception {
+    int i = 10;
+    try (Journal<TestEntry> journal = createJournal()) {
+      JournalWriter<TestEntry> writer = journal.writer();
+      JournalReader<TestEntry> reader = journal.openReader(1);
+
+      for (int j = 1; j <= i; j++) {
+        assertEquals(j, writer.append(new TestEntry(32)).index());
+      }
+
+      for (int j = 1; j <= i - 2; j++) {
+        assertTrue(reader.hasNext());
+        assertEquals(j, reader.next().index());
+      }
+
+      writer.truncate(i - 2);
+
+      assertFalse(reader.hasNext());
+      assertEquals(i - 1, writer.append(new TestEntry(32)).index());
+      assertEquals(i, writer.append(new TestEntry(32)).index());
+
+      assertTrue(reader.hasNext());
+      Indexed<TestEntry> entry = reader.next();
+      assertEquals(i - 1, entry.index());
+      assertTrue(reader.hasNext());
+      entry = reader.next();
+      assertEquals(i, entry.index());
+    }
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testWriteReadEntries() throws Exception {
+    try (Journal<TestEntry> journal = createJournal()) {
+      JournalWriter<TestEntry> writer = journal.writer();
+      JournalReader<TestEntry> reader = journal.openReader(1);
+
+      for (int i = 1; i <= entriesPerSegment * 5; i++) {
+        writer.append(ENTRY);
+        assertTrue(reader.hasNext());
+        Indexed<TestEntry> entry;
+        entry = (Indexed) reader.next();
+        assertEquals(i, entry.index());
+        assertEquals(32, entry.entry().bytes().length);
+        reader.reset(i);
+        entry = (Indexed) reader.next();
+        assertEquals(i, entry.index());
+        assertEquals(32, entry.entry().bytes().length);
+
+        if (i > 6) {
+          reader.reset(i - 5);
+          assertNotNull(reader.getCurrentEntry());
+          assertEquals(i - 6, reader.getCurrentIndex());
+          assertEquals(i - 6, reader.getCurrentEntry().index());
+          assertEquals(i - 5, reader.getNextIndex());
+          reader.reset(i + 1);
+        }
+
+        writer.truncate(i - 1);
+        writer.append(ENTRY);
+
+        assertTrue(reader.hasNext());
+        reader.reset(i);
+        assertTrue(reader.hasNext());
+        entry = (Indexed) reader.next();
+        assertEquals(i, entry.index());
+        assertEquals(32, entry.entry().bytes().length);
+      }
+    }
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testWriteReadCommittedEntries() throws Exception {
+    try (Journal<TestEntry> journal = createJournal()) {
+      JournalWriter<TestEntry> writer = journal.writer();
+      JournalReader<TestEntry> reader = journal.openReader(1, JournalReader.Mode.COMMITS);
+
+      for (int i = 1; i <= entriesPerSegment * 5; i++) {
+        writer.append(ENTRY);
+        assertFalse(reader.hasNext());
+        writer.commit(i);
+        assertTrue(reader.hasNext());
+        Indexed<TestEntry> entry;
+        entry = (Indexed) reader.next();
+        assertEquals(i, entry.index());
+        assertEquals(32, entry.entry().bytes().length);
+        reader.reset(i);
+        entry = (Indexed) reader.next();
+        assertEquals(i, entry.index());
+        assertEquals(32, entry.entry().bytes().length);
+      }
+    }
+  }
+
+  @Test
+  public void testReadAfterCompact() throws Exception {
+    try (SegmentedJournal<TestEntry> journal = createJournal()) {
+      JournalWriter<TestEntry> writer = journal.writer();
+      JournalReader<TestEntry> uncommittedReader = journal.openReader(1, JournalReader.Mode.ALL);
+      JournalReader<TestEntry> committedReader = journal.openReader(1, JournalReader.Mode.COMMITS);
+
+      for (int i = 1; i <= entriesPerSegment * 10; i++) {
+        assertEquals(i, writer.append(ENTRY).index());
+      }
+
+      assertEquals(1, uncommittedReader.getNextIndex());
+      assertTrue(uncommittedReader.hasNext());
+      assertEquals(1, committedReader.getNextIndex());
+      assertFalse(committedReader.hasNext());
+
+      writer.commit(entriesPerSegment * 9);
+
+      assertTrue(uncommittedReader.hasNext());
+      assertTrue(committedReader.hasNext());
+
+      for (int i = 1; i <= entriesPerSegment * 2.5; i++) {
+        assertEquals(i, uncommittedReader.next().index());
+        assertEquals(i, committedReader.next().index());
+      }
+
+      journal.compact(entriesPerSegment * 5 + 1);
+
+      assertNull(uncommittedReader.getCurrentEntry());
+      assertEquals(0, uncommittedReader.getCurrentIndex());
+      assertTrue(uncommittedReader.hasNext());
+      assertEquals(entriesPerSegment * 5 + 1, uncommittedReader.getNextIndex());
+      assertEquals(entriesPerSegment * 5 + 1, uncommittedReader.next().index());
+
+      assertNull(committedReader.getCurrentEntry());
+      assertEquals(0, committedReader.getCurrentIndex());
+      assertTrue(committedReader.hasNext());
+      assertEquals(entriesPerSegment * 5 + 1, committedReader.getNextIndex());
+      assertEquals(entriesPerSegment * 5 + 1, committedReader.next().index());
+    }
+  }
+
+  @Before
+  @After
+  public void cleanupStorage() throws IOException {
+    if (Files.exists(PATH)) {
+      Files.walkFileTree(PATH, new SimpleFileVisitor<Path>() {
+        @Override
+        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+          Files.delete(file);
+          return FileVisitResult.CONTINUE;
+        }
+
+        @Override
+        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+          Files.delete(dir);
+          return FileVisitResult.CONTINUE;
+        }
+      });
+    }
+  }
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/DiskJournalTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/DiskJournalTest.java
new file mode 100644 (file)
index 0000000..6a48f9a
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import io.atomix.storage.StorageLevel;
+
+/**
+ * Disk journal test.
+ */
+public class DiskJournalTest extends PersistentJournalTest {
+  public DiskJournalTest(int maxSegmentSize, int cacheSize) {
+    super(maxSegmentSize, cacheSize);
+  }
+
+  @Override
+  protected StorageLevel storageLevel() {
+    return StorageLevel.DISK;
+  }
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/JournalSegmentDescriptorTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/JournalSegmentDescriptorTest.java
new file mode 100644 (file)
index 0000000..c838954
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Segment descriptor test.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class JournalSegmentDescriptorTest {
+
+  /**
+   * Tests the segment descriptor builder.
+   */
+  @Test
+  public void testDescriptorBuilder() {
+    JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder(ByteBuffer.allocate(JournalSegmentDescriptor.BYTES))
+        .withId(2)
+        .withIndex(1025)
+        .withMaxSegmentSize(1024 * 1024)
+        .withMaxEntries(2048)
+        .build();
+
+    assertEquals(2, descriptor.id());
+    assertEquals(JournalSegmentDescriptor.VERSION, descriptor.version());
+    assertEquals(1025, descriptor.index());
+    assertEquals(1024 * 1024, descriptor.maxSegmentSize());
+    assertEquals(2048, descriptor.maxEntries());
+
+    assertEquals(0, descriptor.updated());
+    long time = System.currentTimeMillis();
+    descriptor.update(time);
+    assertEquals(time, descriptor.updated());
+  }
+
+  /**
+   * Tests copying the segment descriptor.
+   */
+  @Test
+  public void testDescriptorCopy() {
+    JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
+        .withId(2)
+        .withIndex(1025)
+        .withMaxSegmentSize(1024 * 1024)
+        .withMaxEntries(2048)
+        .build();
+
+    long time = System.currentTimeMillis();
+    descriptor.update(time);
+
+    descriptor = descriptor.copyTo(ByteBuffer.allocate(JournalSegmentDescriptor.BYTES));
+
+    assertEquals(2, descriptor.id());
+    assertEquals(JournalSegmentDescriptor.VERSION, descriptor.version());
+    assertEquals(1025, descriptor.index());
+    assertEquals(1024 * 1024, descriptor.maxSegmentSize());
+    assertEquals(2048, descriptor.maxEntries());
+    assertEquals(time, descriptor.updated());
+  }
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/JournalSegmentFileTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/JournalSegmentFileTest.java
new file mode 100644 (file)
index 0000000..d8e2421
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import java.io.File;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Journal segment file test.
+ */
+public class JournalSegmentFileTest {
+
+  @Test
+  public void testIsSegmentFile() throws Exception {
+    assertTrue(JournalSegmentFile.isSegmentFile("foo", "foo-1.log"));
+    assertFalse(JournalSegmentFile.isSegmentFile("foo", "bar-1.log"));
+    assertTrue(JournalSegmentFile.isSegmentFile("foo", "foo-1-1.log"));
+  }
+
+  @Test
+  public void testCreateSegmentFile() throws Exception {
+    File file = JournalSegmentFile.createSegmentFile("foo", new File(System.getProperty("user.dir")), 1);
+    assertTrue(JournalSegmentFile.isSegmentFile("foo", file));
+  }
+
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/MappedJournalTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/MappedJournalTest.java
new file mode 100644 (file)
index 0000000..053c982
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import io.atomix.storage.StorageLevel;
+
+/**
+ * Memory mapped journal test.
+ */
+public class MappedJournalTest extends PersistentJournalTest {
+  public MappedJournalTest(int maxSegmentSize, int cacheSize) {
+    super(maxSegmentSize, cacheSize);
+  }
+
+  @Override
+  protected StorageLevel storageLevel() {
+    return StorageLevel.MAPPED;
+  }
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/MemoryJournalTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/MemoryJournalTest.java
new file mode 100644 (file)
index 0000000..61c58a9
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import io.atomix.storage.StorageLevel;
+
+/**
+ * Memory journal test.
+ */
+public class MemoryJournalTest extends AbstractJournalTest {
+  public MemoryJournalTest(int maxSegmentSize, int cacheSize) {
+    super(maxSegmentSize, cacheSize);
+  }
+
+  @Override
+  protected StorageLevel storageLevel() {
+    return StorageLevel.MEMORY;
+  }
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/PersistentJournalTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/PersistentJournalTest.java
new file mode 100644 (file)
index 0000000..e76f561
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Persistent journal test base.
+ */
+public abstract class PersistentJournalTest extends AbstractJournalTest {
+  protected PersistentJournalTest(int maxSegmentSize, int cacheSize) {
+    super(maxSegmentSize, cacheSize);
+  }
+
+  /**
+   * Tests reading from a compacted journal.
+   */
+  @Test
+  public void testCompactAndRecover() throws Exception {
+    SegmentedJournal<TestEntry> journal = createJournal();
+
+    // Write three segments to the journal.
+    JournalWriter<TestEntry> writer = journal.writer();
+    for (int i = 0; i < entriesPerSegment * 3; i++) {
+      writer.append(ENTRY);
+    }
+
+    // Commit the entries and compact the first segment.
+    writer.commit(entriesPerSegment * 3);
+    journal.compact(entriesPerSegment + 1);
+
+    // Close the journal.
+    journal.close();
+
+    // Reopen the journal and create a reader.
+    journal = createJournal();
+    writer = journal.writer();
+    JournalReader<TestEntry> reader = journal.openReader(1, JournalReader.Mode.COMMITS);
+    writer.append(ENTRY);
+    writer.append(ENTRY);
+    writer.commit(entriesPerSegment * 3);
+
+    // Ensure the reader starts at the first physical index in the journal.
+    assertEquals(entriesPerSegment + 1, reader.getNextIndex());
+    assertEquals(reader.getFirstIndex(), reader.getNextIndex());
+    assertTrue(reader.hasNext());
+    assertEquals(entriesPerSegment + 1, reader.getNextIndex());
+    assertEquals(reader.getFirstIndex(), reader.getNextIndex());
+    assertEquals(entriesPerSegment + 1, reader.next().index());
+  }
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/TestEntry.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/TestEntry.java
new file mode 100644 (file)
index 0000000..dfbc004
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal;
+
+import io.atomix.utils.misc.ArraySizeHashPrinter;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Test entry.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class TestEntry {
+  private final byte[] bytes;
+
+  public TestEntry(int size) {
+    this(new byte[size]);
+  }
+
+  public TestEntry(byte[] bytes) {
+    this.bytes = bytes;
+  }
+
+  public byte[] bytes() {
+    return bytes;
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("bytes", ArraySizeHashPrinter.of(bytes))
+        .toString();
+  }
+}
diff --git a/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/index/SparseJournalIndexTest.java b/third-party/atomix/storage/src/test/java/io/atomix/storage/journal/index/SparseJournalIndexTest.java
new file mode 100644 (file)
index 0000000..8314463
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.storage.journal.index;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Sparse journal index test.
+ */
+public class SparseJournalIndexTest {
+  @Test
+  public void testSparseJournalIndex() throws Exception {
+    JournalIndex index = new SparseJournalIndex(.2);
+    assertNull(index.lookup(1));
+    index.index(1, 2);
+    assertNull(index.lookup(1));
+    index.index(2, 4);
+    index.index(3, 6);
+    index.index(4, 8);
+    index.index(5, 10);
+    assertEquals(5, index.lookup(5).index());
+    assertEquals(10, index.lookup(5).position());
+    index.index(6, 12);
+    index.index(7, 14);
+    index.index(8, 16);
+    assertEquals(5, index.lookup(8).index());
+    assertEquals(10, index.lookup(8).position());
+    index.index(9, 18);
+    index.index(10, 20);
+    assertEquals(10, index.lookup(10).index());
+    assertEquals(20, index.lookup(10).position());
+    index.truncate(8);
+    assertEquals(5, index.lookup(8).index());
+    assertEquals(10, index.lookup(8).position());
+    assertEquals(5, index.lookup(10).index());
+    assertEquals(10, index.lookup(10).position());
+    index.truncate(4);
+    assertNull(index.lookup(4));
+    assertNull(index.lookup(8));
+
+    index = new SparseJournalIndex(.2);
+    assertNull(index.lookup(100));
+    index.index(101, 2);
+    assertNull(index.lookup(1));
+    index.index(102, 4);
+    index.index(103, 6);
+    index.index(104, 8);
+    index.index(105, 10);
+    assertEquals(105, index.lookup(105).index());
+    assertEquals(10, index.lookup(105).position());
+    index.index(106, 12);
+    index.index(107, 14);
+    index.index(108, 16);
+    assertEquals(105, index.lookup(108).index());
+    assertEquals(10, index.lookup(108).position());
+    index.index(109, 18);
+    index.index(110, 20);
+    assertEquals(110, index.lookup(110).index());
+    assertEquals(20, index.lookup(110).position());
+    index.truncate(108);
+    assertEquals(105, index.lookup(108).index());
+    assertEquals(10, index.lookup(108).position());
+    assertEquals(105, index.lookup(110).index());
+    assertEquals(10, index.lookup(110).position());
+    index.truncate(104);
+    assertNull(index.lookup(104));
+    assertNull(index.lookup(108));
+  }
+}
diff --git a/third-party/atomix/storage/src/test/resources/logback.xml b/third-party/atomix/storage/src/test/resources/logback.xml
new file mode 100644 (file)
index 0000000..41f8f99
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+  ~ Copyright 2017-present Open Networking Laboratory
+  ~
+  ~ 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.
+  -->
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+            </pattern>
+        </encoder>
+    </appender>
+
+    <logger name="io.atomix.storage" level="INFO" />
+
+    <root level="${root.logging.level:-INFO}">
+        <appender-ref ref="STDOUT" />
+    </root>
+</configuration>
\ No newline at end of file
diff --git a/third-party/atomix/utils/pom.xml b/third-party/atomix/utils/pom.xml
new file mode 100644 (file)
index 0000000..6867591
--- /dev/null
@@ -0,0 +1,81 @@
+<!--
+  ~ Copyright 2017-present Open Networking Foundation
+  ~
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>io.atomix</groupId>
+    <artifactId>atomix-parent</artifactId>
+    <version>3.2.0-SNAPSHOT</version>
+  </parent>
+
+  <packaging>bundle</packaging>
+  <artifactId>atomix-utils</artifactId>
+  <name>Atomix Utilities</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <version>${guava.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+      <version>${commons.lang3.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-math3</artifactId>
+      <version>${commons.math3.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.esotericsoftware</groupId>
+      <artifactId>kryo</artifactId>
+      <version>${kryo.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.typesafe</groupId>
+      <artifactId>config</artifactId>
+      <version>${config.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>io.github.classgraph</groupId>
+      <artifactId>classgraph</artifactId>
+      <version>${classgraph.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Export-Package>
+              io.atomix.utils.*
+            </Export-Package>
+            <Import-Package>
+              sun.nio.ch;resolution:=optional,sun.misc;resolution:=optional,*
+            </Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/AbstractIdentifier.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/AbstractIdentifier.java
new file mode 100644 (file)
index 0000000..c6d917a
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstract identifier backed by another value, e.g. string, int.
+ */
+public class AbstractIdentifier<T extends Comparable<T>> implements Identifier<T> {
+
+  protected final T identifier; // backing identifier value
+
+  /**
+   * Constructor for serialization.
+   */
+  protected AbstractIdentifier() {
+    this.identifier = null;
+  }
+
+  /**
+   * Constructs an identifier backed by the specified value.
+   *
+   * @param value the backing value
+   */
+  protected AbstractIdentifier(T value) {
+    this.identifier = checkNotNull(value, "Identifier cannot be null.");
+  }
+
+  /**
+   * Returns the backing identifier value.
+   *
+   * @return identifier
+   */
+  public T id() {
+    return identifier;
+  }
+
+  /**
+   * Returns the hashcode of the identifier.
+   *
+   * @return hashcode
+   */
+  @Override
+  public int hashCode() {
+    return identifier.hashCode();
+  }
+
+  /**
+   * Compares two device key identifiers for equality.
+   *
+   * @param obj to compare against
+   * @return true if the objects are equal, false otherwise.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj instanceof AbstractIdentifier) {
+      AbstractIdentifier that = (AbstractIdentifier) obj;
+      return this.getClass() == that.getClass()
+          && Objects.equals(this.identifier, that.identifier);
+    }
+    return false;
+  }
+
+  /**
+   * Returns a string representation of a DeviceKeyId.
+   *
+   * @return string
+   */
+  public String toString() {
+    return identifier.toString();
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/AbstractNamed.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/AbstractNamed.java
new file mode 100644 (file)
index 0000000..cc2db37
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+/**
+ * Abstract named object.
+ */
+public abstract class AbstractNamed implements Named {
+  private String name;
+
+  protected AbstractNamed() {
+    this(null);
+  }
+
+  protected AbstractNamed(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public String name() {
+    return name;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/AtomixIOException.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/AtomixIOException.java
new file mode 100644 (file)
index 0000000..c95f7b8
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+/**
+ * Atomix I/O exception.
+ */
+public class AtomixIOException extends AtomixRuntimeException {
+  public AtomixIOException() {
+  }
+
+  public AtomixIOException(String message) {
+    super(message);
+  }
+
+  public AtomixIOException(String message, Object... args) {
+    super(message, args);
+  }
+
+  public AtomixIOException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public AtomixIOException(Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/AtomixRuntimeException.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/AtomixRuntimeException.java
new file mode 100644 (file)
index 0000000..d3fc88d
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+/**
+ * Atomix runtime exception.
+ */
+public class AtomixRuntimeException extends RuntimeException {
+  public AtomixRuntimeException() {
+  }
+
+  public AtomixRuntimeException(String message) {
+    super(message);
+  }
+
+  public AtomixRuntimeException(String message, Object... args) {
+    super(String.format(message, args));
+  }
+
+  public AtomixRuntimeException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public AtomixRuntimeException(Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/Builder.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/Builder.java
new file mode 100644 (file)
index 0000000..1d44c79
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+/**
+ * Object builder.
+ * <p>
+ * This is a base interface for building objects in Catalyst.
+ *
+ * @param <T> type to build
+ */
+public interface Builder<T> {
+
+  /**
+   * Builds the object.
+   * <p>
+   * The returned object may be a new instance of the built class or a recycled instance, depending on the semantics
+   * of the builder implementation. Users should never assume that a builder allocates a new instance.
+   *
+   * @return The built object.
+   */
+  T build();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/ConfiguredType.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/ConfiguredType.java
new file mode 100644 (file)
index 0000000..b90eb95
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import io.atomix.utils.config.TypedConfig;
+
+/**
+ * Configured type.
+ */
+public interface ConfiguredType<C extends TypedConfig> extends NamedType {
+
+  /**
+   * Returns a new configuration.
+   *
+   * @return a new configuration
+   */
+  C newConfig();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/Generics.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/Generics.java
new file mode 100644 (file)
index 0000000..842d9fd
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * Generics utility.
+ */
+public class Generics {
+
+  /**
+   * Returns the generic type at the given position for the given class.
+   *
+   * @param instance the implementing instance
+   * @param clazz    the generic class
+   * @param position the generic position
+   * @return the generic type at the given position
+   */
+  public static Type getGenericClassType(Object instance, Class<?> clazz, int position) {
+    Class<?> type = instance.getClass();
+    while (type != Object.class) {
+      if (type.getGenericSuperclass() instanceof ParameterizedType) {
+        ParameterizedType genericSuperclass = (ParameterizedType) type.getGenericSuperclass();
+        if (genericSuperclass.getRawType() == clazz) {
+          return genericSuperclass.getActualTypeArguments()[position];
+        } else {
+          type = type.getSuperclass();
+        }
+      } else {
+        type = type.getSuperclass();
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the generic type at the given position for the given interface.
+   *
+   * @param instance the implementing instance
+   * @param iface    the generic interface
+   * @param position the generic position
+   * @return the generic type at the given position
+   */
+  public static Type getGenericInterfaceType(Object instance, Class<?> iface, int position) {
+    Class<?> type = instance.getClass();
+    while (type != Object.class) {
+      for (Type genericType : type.getGenericInterfaces()) {
+        if (genericType instanceof ParameterizedType) {
+          ParameterizedType parameterizedType = (ParameterizedType) genericType;
+          if (parameterizedType.getRawType() == iface) {
+            return parameterizedType.getActualTypeArguments()[position];
+          }
+        }
+      }
+      type = type.getSuperclass();
+    }
+    return null;
+  }
+
+  private Generics() {
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/Identifier.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/Identifier.java
new file mode 100644 (file)
index 0000000..46321a6
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+/**
+ * Abstract identifier backed by another value, e.g. string, int.
+ */
+public interface Identifier<T extends Comparable<T>> {
+
+  /**
+   * Returns the backing identifier value.
+   *
+   * @return identifier
+   */
+  T id();
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/Managed.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/Managed.java
new file mode 100644 (file)
index 0000000..d79a038
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Interface for types that can be asynchronously started and stopped.
+ *
+ * @param <T> managed type
+ */
+public interface Managed<T> {
+
+  /**
+   * Starts the managed object.
+   *
+   * @return A completable future to be completed once the object has been started.
+   */
+  CompletableFuture<T> start();
+
+  /**
+   * Returns a boolean value indicating whether the managed object is running.
+   *
+   * @return Indicates whether the managed object is running.
+   */
+  boolean isRunning();
+
+  /**
+   * Stops the managed object.
+   *
+   * @return A completable future to be completed once the object has been stopped.
+   */
+  CompletableFuture<Void> stop();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/Named.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/Named.java
new file mode 100644 (file)
index 0000000..64cbf99
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+/**
+ * Named object.
+ */
+public interface Named {
+
+  /**
+   * Returns the object name.
+   *
+   * @return the object name
+   */
+  String name();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/NamedType.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/NamedType.java
new file mode 100644 (file)
index 0000000..17b9cab
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+/**
+ * Named type.
+ */
+public interface NamedType extends Named, Type {
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/ServiceException.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/ServiceException.java
new file mode 100644 (file)
index 0000000..6d37087
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+/**
+ * Service exception.
+ */
+public class ServiceException extends AtomixRuntimeException {
+  public ServiceException() {
+  }
+
+  public ServiceException(String message) {
+    super(message);
+  }
+
+  public ServiceException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/Type.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/Type.java
new file mode 100644 (file)
index 0000000..e646220
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+/**
+ * Identifier interface for types.
+ */
+public interface Type {
+
+  /**
+   * Returns the type name.
+   *
+   * @return the type name
+   */
+  String name();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/Version.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/Version.java
new file mode 100644 (file)
index 0000000..914ed0b
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import com.google.common.collect.ComparisonChain;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.Integer.parseInt;
+
+/**
+ * Atomix software version.
+ */
+public final class Version implements Comparable<Version> {
+
+  /**
+   * Returns a new version from the given version string.
+   *
+   * @param version the version string
+   * @return the version object
+   * @throws IllegalArgumentException if the version string is invalid
+   */
+  public static Version from(String version) {
+    String[] fields = version.split("[.-]", 4);
+    checkArgument(fields.length >= 3, "version number is invalid");
+    return new Version(
+        parseInt(fields[0]),
+        parseInt(fields[1]),
+        parseInt(fields[2]),
+        fields.length == 4 ? fields[3] : null);
+  }
+
+  /**
+   * Returns a new version from the given parts.
+   *
+   * @param major the major version number
+   * @param minor the minor version number
+   * @param patch the patch version number
+   * @param build the build version number
+   * @return the version object
+   */
+  public static Version from(int major, int minor, int patch, String build) {
+    return new Version(major, minor, patch, build);
+  }
+
+  private final int major;
+  private final int minor;
+  private final int patch;
+  private final String build;
+
+  private Version(int major, int minor, int patch, String build) {
+    checkArgument(major >= 0, "major version must be >= 0");
+    checkArgument(minor >= 0, "minor version must be >= 0");
+    checkArgument(patch >= 0, "patch version must be >= 0");
+    this.major = major;
+    this.minor = minor;
+    this.patch = patch;
+    this.build = Build.from(build).toString();
+  }
+
+  /**
+   * Returns the major version number.
+   *
+   * @return the major version number
+   */
+  public int major() {
+    return major;
+  }
+
+  /**
+   * Returns the minor version number.
+   *
+   * @return the minor version number
+   */
+  public int minor() {
+    return minor;
+  }
+
+  /**
+   * Returns the patch version number.
+   *
+   * @return the patch version number
+   */
+  public int patch() {
+    return patch;
+  }
+
+  /**
+   * Returns the build version number.
+   *
+   * @return the build version number
+   */
+  public String build() {
+    return build;
+  }
+
+  @Override
+  public int compareTo(Version that) {
+    return ComparisonChain.start()
+        .compare(this.major, that.major)
+        .compare(this.minor, that.minor)
+        .compare(this.patch, that.patch)
+        .compare(Build.from(this.build), Build.from(that.build))
+        .result();
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(major, minor, patch, build);
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == this) {
+      return true;
+    }
+    if (!(object instanceof Version)) {
+      return false;
+    }
+    Version that = (Version) object;
+    return this.major == that.major
+        && this.minor == that.minor
+        && this.patch == that.patch
+        && Objects.equals(this.build, that.build);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder()
+        .append(major)
+        .append('.')
+        .append(minor)
+        .append('.')
+        .append(patch);
+    String build = Build.from(this.build).toString();
+    if (build != null) {
+      builder.append('-').append(build);
+    }
+    return builder.toString();
+  }
+
+  /**
+   * Build version.
+   */
+  private static class Build implements Comparable<Build> {
+
+    /**
+     * Creates a new build version from the given string.
+     *
+     * @param build the build version string
+     * @return the build version
+     * @throws IllegalArgumentException if the build version string is invalid
+     */
+    public static Build from(String build) {
+      if (build == null) {
+        return new Build(Type.FINAL, 0);
+      } else if (build.equalsIgnoreCase(Type.SNAPSHOT.name())) {
+        return new Build(Type.SNAPSHOT, 0);
+      }
+
+      for (Type type : Type.values()) {
+        if (type.name != null && build.length() >= type.name.length() && build.substring(0, type.name.length()).equalsIgnoreCase(type.name)) {
+          try {
+            int version = parseInt(build.substring(type.name.length()));
+            return new Build(type, version);
+          } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(build + " is not a valid build version string");
+          }
+        }
+      }
+      throw new IllegalArgumentException(build + " is not a valid build version string");
+    }
+
+    private final Type type;
+    private final int version;
+
+    private Build(Type type, int version) {
+      this.type = type;
+      this.version = version;
+    }
+
+    @Override
+    public int compareTo(Build that) {
+      return ComparisonChain.start()
+          .compare(this.type.ordinal(), that.type.ordinal())
+          .compare(this.version, that.version)
+          .result();
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(type, version);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+      if (object == this) {
+        return true;
+      }
+      if (!(object instanceof Build)) {
+        return false;
+      }
+      Build that = (Build) object;
+      return Objects.equals(this.type, that.type) && this.version == that.version;
+    }
+
+    @Override
+    public String toString() {
+      return type.format(version);
+    }
+
+    /**
+     * Build type.
+     */
+    private enum Type {
+      SNAPSHOT("snapshot"),
+      ALPHA("alpha"),
+      BETA("beta"),
+      RC("rc"),
+      FINAL(null);
+
+      private final String name;
+
+      Type(String name) {
+        this.name = name;
+      }
+
+      String format(int version) {
+        if (name == null) {
+          return null;
+        } else if ("snapshot".equals(name)) {
+          return "SNAPSHOT";
+        } else {
+          return String.format("%s%d", name, version);
+        }
+      }
+
+      @Override
+      public String toString() {
+        return name;
+      }
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AbstractAccumulator.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AbstractAccumulator.java
new file mode 100644 (file)
index 0000000..9d8b1bc
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Base implementation of an item accumulator. It allows triggering based on
+ * item inter-arrival time threshold, maximum batch life threshold and maximum
+ * batch size.
+ */
+public abstract class AbstractAccumulator<T> implements Accumulator<T> {
+
+  private Logger log = LoggerFactory.getLogger(AbstractAccumulator.class);
+
+  private final Timer timer;
+  private final int maxItems;
+  private final int maxBatchMillis;
+  private final int maxIdleMillis;
+
+  private final AtomicReference<TimerTask> idleTask = new AtomicReference<>();
+  private final AtomicReference<TimerTask> maxTask = new AtomicReference<>();
+
+  private final List<T> items;
+
+  /**
+   * Creates an item accumulator capable of triggering on the specified
+   * thresholds.
+   *
+   * @param timer          timer to use for scheduling check-points
+   * @param maxItems       maximum number of items to accumulate before
+   *                       processing is triggered
+   *                       <p>
+   *                       NB: It is possible that processItems will contain
+   *                       more than maxItems under high load or if isReady()
+   *                       can return false.
+   *                       </p>
+   * @param maxBatchMillis maximum number of millis allowed since the first
+   *                       item before processing is triggered
+   * @param maxIdleMillis  maximum number millis between items before
+   *                       processing is triggered
+   */
+  protected AbstractAccumulator(Timer timer, int maxItems,
+                                int maxBatchMillis, int maxIdleMillis) {
+    this.timer = checkNotNull(timer, "Timer cannot be null");
+
+    checkArgument(maxItems > 1, "Maximum number of items must be > 1");
+    checkArgument(maxBatchMillis > 0, "Maximum millis must be positive");
+    checkArgument(maxIdleMillis > 0, "Maximum idle millis must be positive");
+
+    this.maxItems = maxItems;
+    this.maxBatchMillis = maxBatchMillis;
+    this.maxIdleMillis = maxIdleMillis;
+
+    items = Lists.newArrayListWithExpectedSize(maxItems);
+  }
+
+  @Override
+  public void add(T item) {
+    final int sizeAtTimeOfAdd;
+    synchronized (items) {
+      items.add(item);
+      sizeAtTimeOfAdd = items.size();
+    }
+
+        /*
+            WARNING: It is possible that the item that was just added to the list
+            has been processed by an existing idle task at this point.
+
+            By rescheduling the following timers, it is possible that a
+            superfluous maxTask is generated now OR that the idle task and max
+            task are scheduled at their specified delays. This could result in
+            calls to processItems sooner than expected.
+         */
+
+    // Did we hit the max item threshold?
+    if (sizeAtTimeOfAdd >= maxItems) {
+      if (maxIdleMillis < maxBatchMillis) {
+        cancelTask(idleTask);
+      }
+      rescheduleTask(maxTask, 0 /* now! */);
+    } else {
+      // Otherwise, schedule idle task and if this is a first item
+      // also schedule the max batch age task.
+      if (maxIdleMillis < maxBatchMillis) {
+        rescheduleTask(idleTask, maxIdleMillis);
+      }
+      if (sizeAtTimeOfAdd == 1) {
+        rescheduleTask(maxTask, maxBatchMillis);
+      }
+    }
+  }
+
+  /**
+   * Reschedules the specified task, cancelling existing one if applicable.
+   *
+   * @param taskRef task reference
+   * @param millis  delay in milliseconds
+   */
+  private void rescheduleTask(AtomicReference<TimerTask> taskRef, long millis) {
+    ProcessorTask newTask = new ProcessorTask();
+    timer.schedule(newTask, millis);
+    swapAndCancelTask(taskRef, newTask);
+  }
+
+  /**
+   * Cancels the specified task if it has not run or is not running.
+   *
+   * @param taskRef task reference
+   */
+  private void cancelTask(AtomicReference<TimerTask> taskRef) {
+    swapAndCancelTask(taskRef, null);
+  }
+
+  /**
+   * Sets the new task and attempts to cancelTask the old one.
+   *
+   * @param taskRef task reference
+   * @param newTask new task
+   */
+  private void swapAndCancelTask(AtomicReference<TimerTask> taskRef,
+                                 TimerTask newTask) {
+    TimerTask oldTask = taskRef.getAndSet(newTask);
+    if (oldTask != null) {
+      oldTask.cancel();
+    }
+  }
+
+  // Task for triggering processing of accumulated items
+  private class ProcessorTask extends TimerTask {
+    @Override
+    public void run() {
+      try {
+        if (isReady()) {
+
+          List<T> batch = finalizeCurrentBatch();
+          if (!batch.isEmpty()) {
+            processItems(batch);
+          }
+        } else {
+          rescheduleTask(idleTask, maxIdleMillis);
+        }
+      } catch (Exception e) {
+        log.warn("Unable to process batch due to", e);
+      }
+    }
+  }
+
+  /**
+   * Returns an immutable copy of the existing items and clear the list.
+   *
+   * @return list of existing items
+   */
+  private List<T> finalizeCurrentBatch() {
+    List<T> finalizedList;
+    synchronized (items) {
+      finalizedList = ImmutableList.copyOf(items);
+      items.clear();
+            /*
+             * To avoid reprocessing being triggered on an empty list.
+             */
+      cancelTask(maxTask);
+      cancelTask(idleTask);
+    }
+    return finalizedList;
+  }
+
+  @Override
+  public boolean isReady() {
+    return true;
+  }
+
+  /**
+   * Returns the backing timer.
+   *
+   * @return backing timer
+   */
+  public Timer timer() {
+    return timer;
+  }
+
+  /**
+   * Returns the maximum number of items allowed to accumulate before
+   * processing is triggered.
+   *
+   * @return max number of items
+   */
+  public int maxItems() {
+    return maxItems;
+  }
+
+  /**
+   * Returns the maximum number of millis allowed to expire since the first
+   * item before processing is triggered.
+   *
+   * @return max number of millis a batch is allowed to last
+   */
+  public int maxBatchMillis() {
+    return maxBatchMillis;
+  }
+
+  /**
+   * Returns the maximum number of millis allowed to expire since the last
+   * item arrival before processing is triggered.
+   *
+   * @return max number of millis since the last item
+   */
+  public int maxIdleMillis() {
+    return maxIdleMillis;
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AbstractThreadContext.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AbstractThreadContext.java
new file mode 100644 (file)
index 0000000..41846e2
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+/**
+ * Abstract thread context.
+ */
+public abstract class AbstractThreadContext implements ThreadContext {
+  private volatile boolean blocked;
+
+  @Override
+  public boolean isBlocked() {
+    return blocked;
+  }
+
+  @Override
+  public void block() {
+    blocked = true;
+  }
+
+  @Override
+  public void unblock() {
+    blocked = false;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Accumulator.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Accumulator.java
new file mode 100644 (file)
index 0000000..e949e90
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.List;
+
+/**
+ * Abstraction of an accumulator capable of collecting items and at some
+ * point in time triggers processing of all previously accumulated items.
+ *
+ * @param <T> item type
+ */
+public interface Accumulator<T> {
+
+  /**
+   * Adds an item to the current batch. This operation may, or may not
+   * trigger processing of the current batch of items.
+   *
+   * @param item item to be added to the current batch
+   */
+  void add(T item);
+
+  /**
+   * Processes the specified list of accumulated items.
+   *
+   * @param items list of accumulated items
+   */
+  void processItems(List<T> items);
+
+  /**
+   * Indicates whether the accumulator is ready to process items.
+   *
+   * @return true if ready to process
+   */
+  boolean isReady();
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AtomixFuture.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AtomixFuture.java
new file mode 100644 (file)
index 0000000..fb77855
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * A {@link CompletableFuture} that tracks whether the future or one of its descendants has been blocked on
+ * a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call.
+ */
+public class AtomixFuture<T> extends CompletableFuture<T> {
+
+  /**
+   * Wraps the given future in a new blockable future.
+   *
+   * @param future the future to wrap
+   * @param <T>    the future value type
+   * @return a new blockable future
+   */
+  public static <T> AtomixFuture<T> wrap(CompletableFuture<T> future) {
+    AtomixFuture<T> newFuture = new AtomixFuture<>();
+    future.whenComplete((result, error) -> {
+      if (error == null) {
+        newFuture.complete(result);
+      } else {
+        newFuture.completeExceptionally(error);
+      }
+    });
+    return newFuture;
+  }
+
+  /**
+   * Returns a new completed Atomix future.
+   *
+   * @param result the future result
+   * @param <T>    the future result type
+   * @return the completed future
+   */
+  public static <T> CompletableFuture<T> completedFuture(T result) {
+    CompletableFuture<T> future = new AtomixFuture<>();
+    future.complete(result);
+    return future;
+  }
+
+  /**
+   * Returns a new exceptionally completed Atomix future.
+   *
+   * @param t   the future exception
+   * @param <T> the future result type
+   * @return the completed future
+   */
+  public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
+    CompletableFuture<T> future = new AtomixFuture<>();
+    future.completeExceptionally(t);
+    return future;
+  }
+
+  private static final ThreadContext NULL_CONTEXT = new NullThreadContext();
+
+  private ThreadContext getThreadContext() {
+    ThreadContext context = ThreadContext.currentContext();
+    return context != null ? context : NULL_CONTEXT;
+  }
+
+  @Override
+  public T get() throws InterruptedException, ExecutionException {
+    ThreadContext context = getThreadContext();
+    context.block();
+    try {
+      return super.get();
+    } finally {
+      context.unblock();
+    }
+  }
+
+  @Override
+  public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+    ThreadContext context = getThreadContext();
+    context.block();
+    try {
+      return super.get(timeout, unit);
+    } finally {
+      context.unblock();
+    }
+  }
+
+  @Override
+  public synchronized T join() {
+    ThreadContext context = getThreadContext();
+    context.block();
+    try {
+      return super.join();
+    } finally {
+      context.unblock();
+    }
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn) {
+    return wrap(super.thenApply(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn) {
+    return wrap(super.thenApplyAsync(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {
+    return wrap(super.thenApplyAsync(fn, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
+    return wrap(super.thenAccept(action));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
+    return wrap(super.thenAcceptAsync(action));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {
+    return wrap(super.thenAcceptAsync(action, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenRun(Runnable action) {
+    return wrap(super.thenRun(action));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenRunAsync(Runnable action) {
+    return wrap(super.thenRunAsync(action));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor) {
+    return wrap(super.thenRunAsync(action, executor));
+  }
+
+  @Override
+  public <U, V> CompletableFuture<V> thenCombine(
+      CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) {
+    return wrap(super.thenCombine(other, fn));
+  }
+
+  @Override
+  public <U, V> CompletableFuture<V> thenCombineAsync(
+      CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) {
+    return wrap(super.thenCombineAsync(other, fn));
+  }
+
+  @Override
+  public <U, V> CompletableFuture<V> thenCombineAsync(
+      CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn, Executor executor) {
+    return wrap(super.thenCombineAsync(other, fn, executor));
+  }
+
+  @Override
+  public <U> CompletableFuture<Void> thenAcceptBoth(
+      CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) {
+    return wrap(super.thenAcceptBoth(other, action));
+  }
+
+  @Override
+  public <U> CompletableFuture<Void> thenAcceptBothAsync(
+      CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) {
+    return wrap(super.thenAcceptBothAsync(other, action));
+  }
+
+  @Override
+  public <U> CompletableFuture<Void> thenAcceptBothAsync(
+      CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action, Executor executor) {
+    return wrap(super.thenAcceptBothAsync(other, action, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {
+    return wrap(super.runAfterBoth(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {
+    return wrap(super.runAfterBothAsync(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {
+    return wrap(super.runAfterBothAsync(other, action, executor));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {
+    return wrap(super.applyToEither(other, fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {
+    return wrap(super.applyToEitherAsync(other, fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> applyToEitherAsync(
+      CompletionStage<? extends T> other, Function<? super T, U> fn, Executor executor) {
+    return wrap(super.applyToEitherAsync(other, fn, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action) {
+    return wrap(super.acceptEither(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action) {
+    return wrap(super.acceptEitherAsync(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> acceptEitherAsync(
+      CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor) {
+    return wrap(super.acceptEitherAsync(other, action, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterEither(CompletionStage<?> other, Runnable action) {
+    return wrap(super.runAfterEither(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {
+    return wrap(super.runAfterEitherAsync(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {
+    return wrap(super.runAfterEitherAsync(other, action, executor));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
+    return wrap(super.thenCompose(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
+    return wrap(super.thenComposeAsync(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenComposeAsync(
+      Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) {
+    return wrap(super.thenComposeAsync(fn, executor));
+  }
+
+  @Override
+  public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
+    return wrap(super.whenComplete(action));
+  }
+
+  @Override
+  public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
+    return wrap(super.whenCompleteAsync(action));
+  }
+
+  @Override
+  public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor) {
+    return wrap(super.whenCompleteAsync(action, executor));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
+    return wrap(super.handle(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {
+    return wrap(super.handleAsync(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {
+    return wrap(super.handleAsync(fn, executor));
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AtomixThread.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AtomixThread.java
new file mode 100644 (file)
index 0000000..21f47f2
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Atomix thread.
+ * <p>
+ * The Atomix thread primarily serves to store a {@link ThreadContext} for the current thread.
+ * The context is stored in a {@link WeakReference} in order to allow the thread to be garbage collected.
+ * <p>
+ * There is no {@link ThreadContext} associated with the thread when it is first created.
+ * It is the responsibility of thread creators to {@link #setContext(ThreadContext) set} the thread context when appropriate.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class AtomixThread extends Thread {
+  private WeakReference<ThreadContext> context;
+
+  public AtomixThread(Runnable target) {
+    super(target);
+  }
+
+  /**
+   * Sets the thread context.
+   *
+   * @param context The thread context.
+   */
+  public void setContext(ThreadContext context) {
+    this.context = new WeakReference<>(context);
+  }
+
+  /**
+   * Returns the thread context.
+   *
+   * @return The thread {@link ThreadContext} or {@code null} if no context has been configured.
+   */
+  public ThreadContext getContext() {
+    return context != null ? context.get() : null;
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AtomixThreadFactory.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/AtomixThreadFactory.java
new file mode 100644 (file)
index 0000000..eda5a21
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Named thread factory.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class AtomixThreadFactory implements ThreadFactory {
+  @Override
+  public Thread newThread(Runnable r) {
+    return new AtomixThread(r);
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareSingleThreadContext.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareSingleThreadContext.java
new file mode 100644 (file)
index 0000000..3eed503
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadFactory;
+
+import static io.atomix.utils.concurrent.Threads.namedThreads;
+
+/**
+ * Blocking aware single thread context.
+ */
+public class BlockingAwareSingleThreadContext extends SingleThreadContext {
+  private final Executor threadPoolExecutor;
+
+  public BlockingAwareSingleThreadContext(String nameFormat, Executor threadPoolExecutor) {
+    this(namedThreads(nameFormat, LOGGER), threadPoolExecutor);
+  }
+
+  public BlockingAwareSingleThreadContext(ThreadFactory factory, Executor threadPoolExecutor) {
+    super(factory);
+    this.threadPoolExecutor = threadPoolExecutor;
+  }
+
+  @Override
+  public void execute(Runnable command) {
+    if (isBlocked()) {
+      threadPoolExecutor.execute(command);
+    } else {
+      super.execute(command);
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareSingleThreadContextFactory.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareSingleThreadContextFactory.java
new file mode 100644 (file)
index 0000000..894fd14
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import org.slf4j.Logger;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.atomix.utils.concurrent.Threads.namedThreads;
+
+/**
+ * Single thread context factory.
+ */
+public class BlockingAwareSingleThreadContextFactory implements ThreadContextFactory {
+  private final ThreadFactory threadFactory;
+  private final Executor threadPoolExecutor;
+
+  public BlockingAwareSingleThreadContextFactory(String nameFormat, int threadPoolSize, Logger logger) {
+    this(threadPoolSize, namedThreads(nameFormat, logger));
+  }
+
+  public BlockingAwareSingleThreadContextFactory(int threadPoolSize, ThreadFactory threadFactory) {
+    this(threadFactory, Executors.newScheduledThreadPool(threadPoolSize, threadFactory));
+  }
+
+  public BlockingAwareSingleThreadContextFactory(ThreadFactory threadFactory, Executor threadPoolExecutor) {
+    this.threadFactory = checkNotNull(threadFactory);
+    this.threadPoolExecutor = checkNotNull(threadPoolExecutor);
+  }
+
+  @Override
+  public ThreadContext createContext() {
+    return new BlockingAwareSingleThreadContext(threadFactory, threadPoolExecutor);
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareThreadPoolContext.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareThreadPoolContext.java
new file mode 100644 (file)
index 0000000..3e88f18
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Blocking aware thread pool context.
+ */
+public class BlockingAwareThreadPoolContext extends ThreadPoolContext {
+  public BlockingAwareThreadPoolContext(ScheduledExecutorService parent) {
+    super(parent);
+  }
+
+  @Override
+  public void execute(Runnable command) {
+    if (isBlocked()) {
+      parent.execute(command);
+    } else {
+      super.execute(command);
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareThreadPoolContextFactory.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/BlockingAwareThreadPoolContextFactory.java
new file mode 100644 (file)
index 0000000..d69a4dd
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import org.slf4j.Logger;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+
+import static io.atomix.utils.concurrent.Threads.namedThreads;
+
+/**
+ * Thread pool context factory.
+ */
+public class BlockingAwareThreadPoolContextFactory implements ThreadContextFactory {
+  private final ScheduledExecutorService executor;
+
+  public BlockingAwareThreadPoolContextFactory(String name, int threadPoolSize, Logger logger) {
+    this(threadPoolSize, namedThreads(name, logger));
+  }
+
+  public BlockingAwareThreadPoolContextFactory(int threadPoolSize, ThreadFactory threadFactory) {
+    this(Executors.newScheduledThreadPool(threadPoolSize, threadFactory));
+  }
+
+  public BlockingAwareThreadPoolContextFactory(ScheduledExecutorService executor) {
+    this.executor = executor;
+  }
+
+  @Override
+  public ThreadContext createContext() {
+    return new BlockingAwareThreadPoolContext(executor);
+  }
+
+  @Override
+  public void close() {
+    executor.shutdownNow();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ComposableFuture.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ComposableFuture.java
new file mode 100644 (file)
index 0000000..f789018
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * Special implementation of {@link CompletableFuture} with missing utility methods.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class ComposableFuture<T> extends CompletableFuture<T> implements BiConsumer<T, Throwable> {
+
+  @Override
+  public void accept(T result, Throwable error) {
+    if (error == null) {
+      complete(result);
+    } else {
+      completeExceptionally(error);
+    }
+  }
+
+  /**
+   * Sets a consumer to be called when the future is failed.
+   *
+   * @param consumer The consumer to call.
+   * @return A new future.
+   */
+  public CompletableFuture<T> except(Consumer<Throwable> consumer) {
+    return whenComplete((result, error) -> {
+      if (error != null) {
+        consumer.accept(error);
+      }
+    });
+  }
+
+  /**
+   * Sets a consumer to be called asynchronously when the future is failed.
+   *
+   * @param consumer The consumer to call.
+   * @return A new future.
+   */
+  public CompletableFuture<T> exceptAsync(Consumer<Throwable> consumer) {
+    return whenCompleteAsync((result, error) -> {
+      if (error != null) {
+        consumer.accept(error);
+      }
+    });
+  }
+
+  /**
+   * Sets a consumer to be called asynchronously when the future is failed.
+   *
+   * @param consumer The consumer to call.
+   * @param executor The executor with which to call the consumer.
+   * @return A new future.
+   */
+  public CompletableFuture<T> exceptAsync(Consumer<Throwable> consumer, Executor executor) {
+    return whenCompleteAsync((result, error) -> {
+      if (error != null) {
+        consumer.accept(error);
+      }
+    }, executor);
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Futures.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Futures.java
new file mode 100644 (file)
index 0000000..ab3aafc
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BinaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Utilities for creating completed and exceptional futures.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public final class Futures {
+
+  /**
+   * Gets a future result with a default timeout.
+   *
+   * @param future the future to block
+   * @param <T> the future result type
+   * @return the future result
+   * @throws RuntimeException if a future exception occurs
+   */
+  public static <T> T get(Future<T> future) {
+    return get(future, 30, TimeUnit.SECONDS);
+  }
+
+  /**
+   * Gets a future result with a default timeout.
+   *
+   * @param future the future to block
+   * @param timeout the future timeout
+   * @param timeUnit the future timeout time unit
+   * @param <T> the future result type
+   * @return the future result
+   * @throws RuntimeException if a future exception occurs
+   */
+  public static <T> T get(Future<T> future, long timeout, TimeUnit timeUnit) {
+    try {
+      return future.get(timeout, timeUnit);
+    } catch (InterruptedException | ExecutionException | TimeoutException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Creates a future that is synchronously completed.
+   *
+   * @param result The future result.
+   * @return The completed future.
+   */
+  public static <T> CompletableFuture<T> completedFuture(T result) {
+    return CompletableFuture.completedFuture(result);
+  }
+
+  /**
+   * Creates a future that is asynchronously completed.
+   *
+   * @param result   The future result.
+   * @param executor The executor on which to complete the future.
+   * @return The completed future.
+   */
+  public static <T> CompletableFuture<T> completedFutureAsync(T result, Executor executor) {
+    CompletableFuture<T> future = new CompletableFuture<>();
+    executor.execute(() -> future.complete(result));
+    return future;
+  }
+
+  /**
+   * Creates a future that is synchronously completed exceptionally.
+   *
+   * @param t The future exception.
+   * @return The exceptionally completed future.
+   */
+  public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
+    CompletableFuture<T> future = new CompletableFuture<>();
+    future.completeExceptionally(t);
+    return future;
+  }
+
+  /**
+   * Creates a future that is asynchronously completed exceptionally.
+   *
+   * @param t        The future exception.
+   * @param executor The executor on which to complete the future.
+   * @return The exceptionally completed future.
+   */
+  public static <T> CompletableFuture<T> exceptionalFutureAsync(Throwable t, Executor executor) {
+    CompletableFuture<T> future = new CompletableFuture<>();
+    executor.execute(() -> {
+      future.completeExceptionally(t);
+    });
+    return future;
+  }
+
+  /**
+   * Returns a future that completes callbacks in add order.
+   *
+   * @param <T> future value type
+   * @return a new completable future that will complete added callbacks in the order in which they were added
+   */
+  public static <T> CompletableFuture<T> orderedFuture() {
+    return new OrderedFuture<>();
+  }
+
+  /**
+   * Returns a future that completes callbacks in add order.
+   *
+   * @param <T> future value type
+   * @return a new completable future that will complete added callbacks in the order in which they were added
+   */
+  public static <T> CompletableFuture<T> orderedFuture(CompletableFuture<T> future) {
+    CompletableFuture<T> newFuture = new OrderedFuture<>();
+    future.whenComplete((r, e) -> {
+      if (e == null) {
+        newFuture.complete(r);
+      } else {
+        newFuture.completeExceptionally(e);
+      }
+    });
+    return newFuture;
+  }
+
+  /**
+   * Returns a wrapped future that will be completed on the given executor.
+   *
+   * @param future   the future to be completed on the given executor
+   * @param executor the executor with which to complete the future
+   * @param <T>      the future value type
+   * @return a wrapped future to be completed on the given executor
+   */
+  public static <T> CompletableFuture<T> asyncFuture(CompletableFuture<T> future, Executor executor) {
+    CompletableFuture<T> newFuture = new AtomixFuture<>();
+    future.whenComplete((result, error) -> {
+      executor.execute(() -> {
+        if (error == null) {
+          newFuture.complete(result);
+        } else {
+          newFuture.completeExceptionally(error);
+        }
+      });
+    });
+    return newFuture;
+  }
+
+  /**
+   * Returns a new CompletableFuture completed with a list of computed values
+   * when all of the given CompletableFuture complete.
+   *
+   * @param futures the CompletableFutures
+   * @param <T>     value type of CompletableFuture
+   * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> CompletableFuture<Stream<T>> allOf(Stream<CompletableFuture<T>> futures) {
+    CompletableFuture<T>[] futuresArray = futures.toArray(CompletableFuture[]::new);
+    return AtomixFuture.wrap(CompletableFuture.allOf(futuresArray)
+        .thenApply(v -> Stream.of(futuresArray).map(CompletableFuture::join)));
+  }
+
+  /**
+   * Returns a new CompletableFuture completed with a list of computed values
+   * when all of the given CompletableFuture complete.
+   *
+   * @param futures the CompletableFutures
+   * @param <T>     value type of CompletableFuture
+   * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
+   */
+  public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futures) {
+    return AtomixFuture.wrap(CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
+        .thenApply(v -> futures.stream()
+            .map(CompletableFuture::join)
+            .collect(Collectors.toList())));
+  }
+
+  /**
+   * Returns a new CompletableFuture completed by reducing a list of computed values
+   * when all of the given CompletableFuture complete.
+   *
+   * @param futures    the CompletableFutures
+   * @param reducer    reducer for computing the result
+   * @param emptyValue zero value to be returned if the input future list is empty
+   * @param <T>        value type of CompletableFuture
+   * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
+   */
+  public static <T> CompletableFuture<T> allOf(
+      List<CompletableFuture<T>> futures, BinaryOperator<T> reducer, T emptyValue) {
+    return allOf(futures).thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/NullThreadContext.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/NullThreadContext.java
new file mode 100644 (file)
index 0000000..9ee1d75
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.time.Duration;
+
+/**
+ * Null thread context.
+ */
+public class NullThreadContext implements ThreadContext {
+  @Override
+  public Scheduled schedule(Duration delay, Runnable callback) {
+    return null;
+  }
+
+  @Override
+  public Scheduled schedule(Duration initialDelay, Duration interval, Runnable callback) {
+    return null;
+  }
+
+  @Override
+  public boolean isBlocked() {
+    return false;
+  }
+
+  @Override
+  public void block() {
+
+  }
+
+  @Override
+  public void unblock() {
+
+  }
+
+  @Override
+  public void close() {
+
+  }
+
+  @Override
+  public void execute(Runnable command) {
+
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/OrderedExecutor.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/OrderedExecutor.java
new file mode 100644 (file)
index 0000000..c3cee5a
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.LinkedList;
+import java.util.concurrent.Executor;
+
+/**
+ * Executor that executes tasks in order on a shared thread pool.
+ * <p>
+ * The ordered executor behaves semantically like a single-threaded executor, but multiplexes tasks on a shared thread
+ * pool, ensuring blocked threads in the shared thread pool don't block individual ordered executors.
+ */
+public class OrderedExecutor implements Executor {
+  private final Executor parent;
+  private final LinkedList<Runnable> tasks = new LinkedList<>();
+  private boolean running;
+
+  public OrderedExecutor(Executor parent) {
+    this.parent = parent;
+  }
+
+  private void run() {
+    for (;;) {
+      final Runnable task;
+      synchronized (tasks) {
+        task = tasks.poll();
+        if (task == null) {
+          running = false;
+          return;
+        }
+      }
+      task.run();
+    }
+  }
+
+  @Override
+  public void execute(Runnable command) {
+    synchronized (tasks) {
+      tasks.add(command);
+      if (!running) {
+        running = true;
+        parent.execute(this::run);
+      }
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/OrderedFuture.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/OrderedFuture.java
new file mode 100644 (file)
index 0000000..f9935ca
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * A {@link CompletableFuture} that ensures callbacks are called in FIFO order.
+ * <p>
+ * The default {@link CompletableFuture} does not guarantee the ordering of callbacks, and indeed appears to
+ * execute them in LIFO order.
+ */
+public class OrderedFuture<T> extends CompletableFuture<T> {
+
+  /**
+   * Wraps the given future in a new blockable future.
+   *
+   * @param future the future to wrap
+   * @param <T>    the future value type
+   * @return a new blockable future
+   */
+  public static <T> CompletableFuture<T> wrap(CompletableFuture<T> future) {
+    CompletableFuture<T> newFuture = new OrderedFuture<>();
+    future.whenComplete((result, error) -> {
+      if (error == null) {
+        newFuture.complete(result);
+      } else {
+        newFuture.completeExceptionally(error);
+      }
+    });
+    return newFuture;
+  }
+
+  private static final ThreadContext NULL_CONTEXT = new NullThreadContext();
+
+  private final Queue<CompletableFuture<T>> orderedFutures = new LinkedList<>();
+  private volatile boolean complete;
+  private volatile T result;
+  private volatile Throwable error;
+
+  public OrderedFuture() {
+    super.whenComplete(this::complete);
+  }
+
+  private ThreadContext getThreadContext() {
+    ThreadContext context = ThreadContext.currentContext();
+    return context != null ? context : NULL_CONTEXT;
+  }
+
+  @Override
+  public T get() throws InterruptedException, ExecutionException {
+    ThreadContext context = getThreadContext();
+    context.block();
+    try {
+      return super.get();
+    } finally {
+      context.unblock();
+    }
+  }
+
+  @Override
+  public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+    ThreadContext context = getThreadContext();
+    context.block();
+    try {
+      return super.get(timeout, unit);
+    } finally {
+      context.unblock();
+    }
+  }
+
+  @Override
+  public synchronized T join() {
+    ThreadContext context = getThreadContext();
+    context.block();
+    try {
+      return super.join();
+    } finally {
+      context.unblock();
+    }
+  }
+
+  /**
+   * Adds a new ordered future.
+   */
+  private CompletableFuture<T> orderedFuture() {
+    if (!complete) {
+      synchronized (orderedFutures) {
+        if (!complete) {
+          CompletableFuture<T> future = new CompletableFuture<>();
+          orderedFutures.add(future);
+          return future;
+        }
+      }
+    }
+
+    // Completed
+    if (error == null) {
+      return CompletableFuture.completedFuture(result);
+    } else {
+      return Futures.exceptionalFuture(error);
+    }
+  }
+
+  /**
+   * Completes futures in FIFO order.
+   */
+  private void complete(T result, Throwable error) {
+    synchronized (orderedFutures) {
+      this.result = result;
+      this.error = error;
+      this.complete = true;
+      if (error == null) {
+        for (CompletableFuture<T> future : orderedFutures) {
+          future.complete(result);
+        }
+      } else {
+        for (CompletableFuture<T> future : orderedFutures) {
+          future.completeExceptionally(error);
+        }
+      }
+      orderedFutures.clear();
+    }
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn) {
+    return wrap(orderedFuture().thenApply(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn) {
+    return wrap(orderedFuture().thenApplyAsync(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {
+    return wrap(orderedFuture().thenApplyAsync(fn, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
+    return wrap(orderedFuture().thenAccept(action));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
+    return wrap(orderedFuture().thenAcceptAsync(action));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {
+    return wrap(orderedFuture().thenAcceptAsync(action, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenRun(Runnable action) {
+    return wrap(orderedFuture().thenRun(action));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenRunAsync(Runnable action) {
+    return wrap(orderedFuture().thenRunAsync(action));
+  }
+
+  @Override
+  public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor) {
+    return wrap(orderedFuture().thenRunAsync(action, executor));
+  }
+
+  @Override
+  public <U, V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) {
+    return wrap(orderedFuture().thenCombine(other, fn));
+  }
+
+  @Override
+  public <U, V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) {
+    return wrap(orderedFuture().thenCombineAsync(other, fn));
+  }
+
+  @Override
+  public <U, V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn, Executor executor) {
+    return wrap(orderedFuture().thenCombineAsync(other, fn, executor));
+  }
+
+  @Override
+  public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) {
+    return wrap(orderedFuture().thenAcceptBoth(other, action));
+  }
+
+  @Override
+  public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) {
+    return wrap(orderedFuture().thenAcceptBothAsync(other, action));
+  }
+
+  @Override
+  public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action, Executor executor) {
+    return wrap(orderedFuture().thenAcceptBothAsync(other, action, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {
+    return wrap(orderedFuture().runAfterBoth(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {
+    return wrap(orderedFuture().runAfterBothAsync(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {
+    return wrap(orderedFuture().runAfterBothAsync(other, action, executor));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {
+    return wrap(orderedFuture().applyToEither(other, fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {
+    return wrap(orderedFuture().applyToEitherAsync(other, fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn, Executor executor) {
+    return wrap(orderedFuture().applyToEitherAsync(other, fn, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action) {
+    return wrap(orderedFuture().acceptEither(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action) {
+    return wrap(orderedFuture().acceptEitherAsync(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor) {
+    return wrap(orderedFuture().acceptEitherAsync(other, action, executor));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterEither(CompletionStage<?> other, Runnable action) {
+    return wrap(orderedFuture().runAfterEither(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {
+    return wrap(orderedFuture().runAfterEitherAsync(other, action));
+  }
+
+  @Override
+  public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {
+    return wrap(orderedFuture().runAfterEitherAsync(other, action, executor));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
+    return wrap(orderedFuture().thenCompose(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
+    return wrap(orderedFuture().thenComposeAsync(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) {
+    return wrap(orderedFuture().thenComposeAsync(fn, executor));
+  }
+
+  @Override
+  public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
+    return wrap(orderedFuture().whenComplete(action));
+  }
+
+  @Override
+  public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
+    return wrap(orderedFuture().whenCompleteAsync(action));
+  }
+
+  @Override
+  public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor) {
+    return wrap(orderedFuture().whenCompleteAsync(action, executor));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
+    return wrap(orderedFuture().handle(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {
+    return wrap(orderedFuture().handleAsync(fn));
+  }
+
+  @Override
+  public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {
+    return wrap(orderedFuture().handleAsync(fn, executor));
+  }
+
+  @Override
+  public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) {
+    return wrap(orderedFuture().exceptionally(fn));
+  }
+
+  @Override
+  public CompletableFuture<T> toCompletableFuture() {
+    return this;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferenceCounted.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferenceCounted.java
new file mode 100644 (file)
index 0000000..447e4cf
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+/**
+ * Reference counting interface.
+ * <p>
+ * Types that implement {@code ReferenceCounted} can be counted for references and thus used to clean up resources once
+ * a given instance of an object is no longer in use.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface ReferenceCounted<T> extends AutoCloseable {
+
+  /**
+   * Acquires a reference.
+   *
+   * @return The acquired reference.
+   */
+  T acquire();
+
+  /**
+   * Releases a reference.
+   *
+   * @return Indicates whether all references to the object have been released.
+   */
+  boolean release();
+
+  /**
+   * Returns the number of open references.
+   *
+   * @return The number of open references.
+   */
+  int references();
+
+  /**
+   * Defines an exception free close implementation.
+   */
+  @Override
+  void close();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferenceFactory.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferenceFactory.java
new file mode 100644 (file)
index 0000000..05454ab
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+/**
+ * Reference factory.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface ReferenceFactory<T extends ReferenceCounted<?>> {
+
+  /**
+   * Creates a new reference.
+   *
+   * @param manager The reference manager.
+   * @return The created reference.
+   */
+  T createReference(ReferenceManager<T> manager);
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferenceManager.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferenceManager.java
new file mode 100644 (file)
index 0000000..0c78d0d
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+/**
+ * Reference manager. Manages {@link ReferenceCounted} objects.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface ReferenceManager<T> {
+
+  /**
+   * Releases the given reference.
+   * <p>
+   * This method should be called with a {@link ReferenceCounted} object that contains no
+   * additional references. This allows, for instance, pools to recycle dereferenced objects.
+   *
+   * @param reference The reference to release.
+   */
+  void release(T reference);
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferencePool.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ReferencePool.java
new file mode 100644 (file)
index 0000000..0006814
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * Pool of reference counted objects.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class ReferencePool<T extends ReferenceCounted<?>> implements ReferenceManager<T>, AutoCloseable {
+  private final ReferenceFactory<T> factory;
+  private final Queue<T> pool = new ConcurrentLinkedQueue<>();
+  private volatile boolean closed;
+
+  public ReferencePool(ReferenceFactory<T> factory) {
+    if (factory == null) {
+      throw new NullPointerException("factory cannot be null");
+    }
+    this.factory = factory;
+  }
+
+  /**
+   * Acquires a reference.
+   *
+   * @return The acquired reference.
+   */
+  public T acquire() {
+    if (closed) {
+      throw new IllegalStateException("pool closed");
+    }
+
+    T reference = pool.poll();
+    if (reference == null) {
+      reference = factory.createReference(this);
+    }
+    reference.acquire();
+    return reference;
+  }
+
+  @Override
+  public void release(T reference) {
+    if (!closed) {
+      pool.add(reference);
+    }
+  }
+
+  @Override
+  public synchronized void close() {
+    if (closed) {
+      throw new IllegalStateException("pool closed");
+    }
+
+    closed = true;
+    for (T reference : pool) {
+      reference.close();
+    }
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Retries.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Retries.java
new file mode 100644 (file)
index 0000000..bd42a2b
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Retry utilities.
+ */
+public final class Retries {
+
+  /**
+   * Returns a function that retries execution on failure.
+   * @param base base function
+   * @param exceptionClass type of exception for which to retry
+   * @param maxRetries max number of retries before giving up
+   * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
+   * the interval (0, maxDelayBetweenRetries]
+   * @return function
+   * @param <U> type of function input
+   * @param <V> type of function output
+   */
+  public static <U, V> Function<U, V> retryable(Function<U, V> base,
+                                                Class<? extends Throwable> exceptionClass,
+                                                int maxRetries,
+                                                int maxDelayBetweenRetries) {
+    return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
+  }
+
+  /**
+   * Returns a Supplier that retries execution on failure.
+   * @param base base supplier
+   * @param exceptionClass type of exception for which to retry
+   * @param maxRetries max number of retries before giving up
+   * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
+   * the interval (0, maxDelayBetweenRetries]
+   * @return supplier
+   * @param <V> type of supplied result
+   */
+  public static <V> Supplier<V> retryable(Supplier<V> base,
+                                          Class<? extends Throwable> exceptionClass,
+                                          int maxRetries,
+                                          int maxDelayBetweenRetries) {
+    return () -> new RetryingFunction<>(v -> base.get(),
+        exceptionClass,
+        maxRetries,
+        maxDelayBetweenRetries).apply(null);
+  }
+
+  /**
+   * Suspends the current thread for a random number of millis between 0 and
+   * the indicated limit.
+   *
+   * @param ms max number of millis
+   */
+  public static void randomDelay(int ms) {
+    try {
+      Thread.sleep(ThreadLocalRandom.current().nextInt(ms));
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Interrupted", e);
+    }
+  }
+
+  /**
+   * Suspends the current thread for a specified number of millis and nanos.
+   *
+   * @param ms    number of millis
+   * @param nanos number of nanos
+   */
+  public static void delay(int ms, int nanos) {
+    try {
+      Thread.sleep(ms, nanos);
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Interrupted", e);
+    }
+  }
+
+  private Retries() {
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/RetryingFunction.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/RetryingFunction.java
new file mode 100644 (file)
index 0000000..9ef40f4
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.function.Function;
+
+import static com.google.common.base.Throwables.throwIfUnchecked;
+
+/**
+ * Function that retries execution on failure.
+ *
+ * @param <U> input type
+ * @param <V> output type
+ */
+public class RetryingFunction<U, V> implements Function<U, V> {
+  private final Function<U, V> baseFunction;
+  private final Class<? extends Throwable> exceptionClass;
+  private final int maxRetries;
+  private final int maxDelayBetweenRetries;
+
+  public RetryingFunction(Function<U, V> baseFunction,
+      Class<? extends Throwable> exceptionClass,
+      int maxRetries,
+      int maxDelayBetweenRetries) {
+    this.baseFunction = baseFunction;
+    this.exceptionClass = exceptionClass;
+    this.maxRetries = maxRetries;
+    this.maxDelayBetweenRetries = maxDelayBetweenRetries;
+  }
+
+  @SuppressWarnings("squid:S1181")
+  // Yes we really do want to catch Throwable
+  @Override
+  public V apply(U input) {
+    int retryAttempts = 0;
+    while (true) {
+      try {
+        return baseFunction.apply(input);
+      } catch (Throwable t) {
+        if (!exceptionClass.isAssignableFrom(t.getClass()) || retryAttempts == maxRetries) {
+          throwIfUnchecked(t);
+          throw new RuntimeException(t);
+        }
+        Retries.randomDelay(maxDelayBetweenRetries);
+        retryAttempts++;
+      }
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Scheduled.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Scheduled.java
new file mode 100644 (file)
index 0000000..02bfd29
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.atomix.utils.concurrent;
+
+/**
+ * Scheduled task.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface Scheduled {
+
+  /**
+   * Cancels the scheduled task.
+   */
+  void cancel();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Scheduler.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Scheduler.java
new file mode 100644 (file)
index 0000000..f581f35
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Scheduler.
+ */
+public interface Scheduler {
+
+  /**
+   * Schedules a runnable after a delay.
+   *
+   * @param delay the delay after which to run the callback
+   * @param timeUnit the time unit
+   * @param callback the callback to run
+   * @return the scheduled callback
+   */
+  default Scheduled schedule(long delay, TimeUnit timeUnit, Runnable callback) {
+    return schedule(Duration.ofMillis(timeUnit.toMillis(delay)), callback);
+  }
+
+  /**
+   * Schedules a runnable after a delay.
+   *
+   * @param delay the delay after which to run the callback
+   * @param callback the callback to run
+   * @return the scheduled callback
+   */
+  Scheduled schedule(Duration delay, Runnable callback);
+
+  /**
+   * Schedules a runnable at a fixed rate.
+   *
+   * @param initialDelay the initial delay
+   * @param interval the interval at which to run the callback
+   * @param timeUnit the time unit
+   * @param callback the callback to run
+   * @return the scheduled callback
+   */
+  default Scheduled schedule(long initialDelay, long interval, TimeUnit timeUnit, Runnable callback) {
+    return schedule(Duration.ofMillis(timeUnit.toMillis(initialDelay)), Duration.ofMillis(timeUnit.toMillis(interval)), callback);
+  }
+
+  /**
+   * Schedules a runnable at a fixed rate.
+   *
+   * @param initialDelay the initial delay
+   * @param interval the interval at which to run the callback
+   * @param callback the callback to run
+   * @return the scheduled callback
+   */
+  Scheduled schedule(Duration initialDelay, Duration interval, Runnable callback);
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/SingleThreadContext.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/SingleThreadContext.java
new file mode 100644 (file)
index 0000000..95ad86d
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.google.common.base.Preconditions.checkState;
+import static io.atomix.utils.concurrent.Threads.namedThreads;
+
+/**
+ * Single threaded context.
+ * <p>
+ * This is a basic {@link ThreadContext} implementation that uses a
+ * {@link ScheduledExecutorService} to schedule events on the context thread.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class SingleThreadContext extends AbstractThreadContext {
+  protected static final Logger LOGGER = LoggerFactory.getLogger(SingleThreadContext.class);
+  private final ScheduledExecutorService executor;
+  private final Executor wrappedExecutor = new Executor() {
+    @Override
+    public void execute(Runnable command) {
+      try {
+        executor.execute(() -> {
+          try {
+            command.run();
+          } catch (Exception e) {
+            LOGGER.error("An uncaught exception occurred", e);
+          }
+        });
+      } catch (RejectedExecutionException e) {
+      }
+    }
+  };
+
+  /**
+   * Creates a new single thread context.
+   * <p>
+   * The provided context name will be passed to {@link AtomixThreadFactory} and used
+   * when instantiating the context thread.
+   *
+   * @param nameFormat The context nameFormat which will be formatted with a thread number.
+   */
+  public SingleThreadContext(String nameFormat) {
+    this(namedThreads(nameFormat, LOGGER));
+  }
+
+  /**
+   * Creates a new single thread context.
+   *
+   * @param factory The thread factory.
+   */
+  public SingleThreadContext(ThreadFactory factory) {
+    this(new ScheduledThreadPoolExecutor(1, factory));
+  }
+
+  /**
+   * Creates a new single thread context.
+   *
+   * @param executor The executor on which to schedule events. This must be a single thread scheduled executor.
+   */
+  protected SingleThreadContext(ScheduledExecutorService executor) {
+    this(getThread(executor), executor);
+  }
+
+  private SingleThreadContext(Thread thread, ScheduledExecutorService executor) {
+    this.executor = executor;
+    checkState(thread instanceof AtomixThread, "not a Catalyst thread");
+    ((AtomixThread) thread).setContext(this);
+  }
+
+  /**
+   * Gets the thread from a single threaded executor service.
+   */
+  protected static AtomixThread getThread(ExecutorService executor) {
+    final AtomicReference<AtomixThread> thread = new AtomicReference<>();
+    try {
+      executor.submit(() -> {
+        thread.set((AtomixThread) Thread.currentThread());
+      }).get();
+    } catch (InterruptedException | ExecutionException e) {
+      throw new IllegalStateException("failed to initialize thread state", e);
+    }
+    return thread.get();
+  }
+
+  @Override
+  public void execute(Runnable command) {
+    wrappedExecutor.execute(command);
+  }
+
+  @Override
+  public Scheduled schedule(Duration delay, Runnable runnable) {
+    ScheduledFuture<?> future = executor.schedule(runnable, delay.toMillis(), TimeUnit.MILLISECONDS);
+    return () -> future.cancel(false);
+  }
+
+  @Override
+  public Scheduled schedule(Duration delay, Duration interval, Runnable runnable) {
+    ScheduledFuture<?> future = executor.scheduleAtFixedRate(runnable, delay.toMillis(), interval.toMillis(), TimeUnit.MILLISECONDS);
+    return () -> future.cancel(false);
+  }
+
+  @Override
+  public void close() {
+    executor.shutdownNow();
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadContext.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadContext.java
new file mode 100644 (file)
index 0000000..2cb4189
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import java.util.concurrent.Executor;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Thread context.
+ * <p>
+ * The thread context is used by Catalyst to determine the correct thread on which to execute asynchronous callbacks.
+ * All threads created within Catalyst must be instances of {@link AtomixThread}. Once
+ * a thread has been created, the context is stored in the thread object via
+ * {@link AtomixThread#setContext(ThreadContext)}. This means there is a one-to-one relationship
+ * between a context and a thread. That is, a context is representative of a thread and provides an interface for firing
+ * events on that thread.
+ * <p>
+ * Components of the framework that provide custom threads should use {@link AtomixThreadFactory}
+ * to allocate new threads and provide a custom {@link ThreadContext} implementation.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface ThreadContext extends AutoCloseable, Executor, Scheduler {
+
+  /**
+   * Returns the current thread context.
+   *
+   * @return The current thread context or {@code null} if no context exists.
+   */
+  static ThreadContext currentContext() {
+    Thread thread = Thread.currentThread();
+    return thread instanceof AtomixThread ? ((AtomixThread) thread).getContext() : null;
+  }
+
+  /**
+   * @throws IllegalStateException if the current thread is not a catalyst thread
+   */
+  static ThreadContext currentContextOrThrow() {
+    ThreadContext context = currentContext();
+    checkState(context != null, "not on a Catalyst thread");
+    return context;
+  }
+
+  /**
+   * Returns a boolean indicating whether the current thread is in this context.
+   *
+   * @return Indicates whether the current thread is in this context.
+   */
+  default boolean isCurrentContext() {
+    return currentContext() == this;
+  }
+
+  /**
+   * Checks that the current thread is the correct context thread.
+   */
+  default void checkThread() {
+    checkState(currentContext() == this, "not on a Catalyst thread");
+  }
+
+  /**
+   * Returns whether the thread context is currently marked blocked.
+   *
+   * @return whether the thread context is currently marked blocked
+   */
+  boolean isBlocked();
+
+  /**
+   * Marks the thread context as blocked.
+   */
+  void block();
+
+  /**
+   * Marks the thread context as unblocked.
+   */
+  void unblock();
+
+  /**
+   * Closes the context.
+   */
+  @Override
+  void close();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadContextFactory.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadContextFactory.java
new file mode 100644 (file)
index 0000000..09b6256
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+/**
+ * Thread context factory.
+ */
+public interface ThreadContextFactory {
+
+  /**
+   * Creates a new thread context.
+   *
+   * @return a new thread context
+   */
+  ThreadContext createContext();
+
+  /**
+   * Closes the factory.
+   */
+  default void close() {
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadModel.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadModel.java
new file mode 100644 (file)
index 0000000..dbbf58c
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import org.slf4j.Logger;
+
+/**
+ * Raft thread model.
+ */
+public enum ThreadModel {
+
+  /**
+   * A thread model that creates a thread pool to be shared by all services.
+   */
+  SHARED_THREAD_POOL {
+    @Override
+    public ThreadContextFactory factory(String nameFormat, int threadPoolSize, Logger logger) {
+      return new BlockingAwareThreadPoolContextFactory(nameFormat, threadPoolSize, logger);
+    }
+  },
+
+  /**
+   * A thread model that creates a thread for each Raft service.
+   */
+  THREAD_PER_SERVICE {
+    @Override
+    public ThreadContextFactory factory(String nameFormat, int threadPoolSize, Logger logger) {
+      return new BlockingAwareSingleThreadContextFactory(nameFormat, threadPoolSize, logger);
+    }
+  };
+
+  /**
+   * Returns a thread context factory.
+   *
+   * @param nameFormat the thread name format
+   * @param threadPoolSize the thread pool size
+   * @param logger the thread logger
+   * @return the thread context factory
+   */
+  public abstract ThreadContextFactory factory(String nameFormat, int threadPoolSize, Logger logger);
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadPoolContext.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/ThreadPoolContext.java
new file mode 100644 (file)
index 0000000..1e3c6f8
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.util.LinkedList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Thread pool context.
+ * <p>
+ * This is a special {@link ThreadContext} implementation that schedules events to be executed
+ * on a thread pool. Events executed by this context are guaranteed to be executed on order but may be executed on different
+ * threads in the provided thread pool.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class ThreadPoolContext extends AbstractThreadContext {
+  private static final Logger LOGGER = LoggerFactory.getLogger(ThreadPoolContext.class);
+  protected final ScheduledExecutorService parent;
+  private final Runnable runner;
+  private final LinkedList<Runnable> tasks = new LinkedList<>();
+  private boolean running;
+  private final Executor executor = new Executor() {
+    @Override
+    public void execute(Runnable command) {
+      synchronized (tasks) {
+        tasks.add(command);
+        if (!running) {
+          running = true;
+          parent.execute(runner);
+        }
+      }
+    }
+  };
+
+  /**
+   * Creates a new thread pool context.
+   *
+   * @param parent The thread pool on which to execute events.
+   */
+  public ThreadPoolContext(ScheduledExecutorService parent) {
+    this.parent = checkNotNull(parent, "parent cannot be null");
+
+    // This code was shamelessly stolededed from Vert.x:
+    // https://github.com/eclipse/vert.x/blob/master/src/main/java/io/vertx/core/impl/OrderedExecutorFactory.java
+    runner = () -> {
+      ((AtomixThread) Thread.currentThread()).setContext(this);
+      for (;;) {
+        final Runnable task;
+        synchronized (tasks) {
+          task = tasks.poll();
+          if (task == null) {
+            running = false;
+            return;
+          }
+        }
+
+        try {
+          task.run();
+        } catch (Throwable t) {
+          LOGGER.error("An uncaught exception occurred", t);
+          throw t;
+        }
+      }
+    };
+  }
+
+  @Override
+  public void execute(Runnable command) {
+    executor.execute(command);
+  }
+
+  @Override
+  public Scheduled schedule(Duration delay, Runnable runnable) {
+    ScheduledFuture<?> future = parent.schedule(() -> executor.execute(runnable), delay.toMillis(), TimeUnit.MILLISECONDS);
+    return () -> future.cancel(false);
+  }
+
+  @Override
+  public Scheduled schedule(Duration delay, Duration interval, Runnable runnable) {
+    ScheduledFuture<?> future = parent.scheduleAtFixedRate(() -> executor.execute(runnable), delay.toMillis(), interval.toMillis(), TimeUnit.MILLISECONDS);
+    return () -> future.cancel(false);
+  }
+
+  @Override
+  public void close() {
+    // Do nothing.
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Threads.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/Threads.java
new file mode 100644 (file)
index 0000000..360ceea
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.slf4j.Logger;
+
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Thread utilities.
+ */
+public final class Threads {
+
+  /**
+   * Returns a thread factory that produces threads named according to the
+   * supplied name pattern.
+   *
+   * @param pattern name pattern
+   * @return thread factory
+   */
+  public static ThreadFactory namedThreads(String pattern, Logger log) {
+    return new ThreadFactoryBuilder()
+        .setNameFormat(pattern)
+        .setThreadFactory(new AtomixThreadFactory())
+        .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
+        .build();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/concurrent/package-info.java
new file mode 100644 (file)
index 0000000..2843b2c
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides classes and interfaces for managing concurrency.
+ */
+package io.atomix.utils.concurrent;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/config/Config.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/config/Config.java
new file mode 100644 (file)
index 0000000..6893d54
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.config;
+
+/**
+ * Atomix configuration.
+ */
+public interface Config {
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/config/ConfigMapper.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/config/ConfigMapper.java
new file mode 100644 (file)
index 0000000..9ef936e
--- /dev/null
@@ -0,0 +1,623 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.config;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigException;
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigList;
+import com.typesafe.config.ConfigMemorySize;
+import com.typesafe.config.ConfigObject;
+import com.typesafe.config.ConfigParseOptions;
+import com.typesafe.config.ConfigValue;
+import io.atomix.utils.Named;
+import io.atomix.utils.memory.MemorySize;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Utility for applying Typesafe configurations to Atomix configuration objects.
+ */
+public class ConfigMapper {
+  private static final Logger LOGGER = LoggerFactory.getLogger(ConfigMapper.class);
+  private final ClassLoader classLoader;
+
+  public ConfigMapper(ClassLoader classLoader) {
+    this.classLoader = classLoader;
+  }
+
+  /**
+   * Loads the given configuration file using the mapper, falling back to the given resources.
+   *
+   * @param type      the type to load
+   * @param files     the files to load
+   * @param resources the resources to which to fall back
+   * @param <T>       the resulting type
+   * @return the loaded configuration
+   */
+  public <T> T loadFiles(Class<T> type, List<File> files, List<String> resources) {
+    if (files == null) {
+      return loadResources(type, resources);
+    }
+
+    Config config = ConfigFactory.systemProperties();
+    for (File file : files) {
+      config = config.withFallback(ConfigFactory.parseFile(file, ConfigParseOptions.defaults().setAllowMissing(false)));
+    }
+
+    for (String resource : resources) {
+      config = config.withFallback(ConfigFactory.load(classLoader, resource));
+    }
+    return map(checkNotNull(config, "config cannot be null").resolve(), type);
+  }
+
+  /**
+   * Loads the given resources using the configuration mapper.
+   *
+   * @param type      the type to load
+   * @param resources the resources to load
+   * @param <T>       the resulting type
+   * @return the loaded configuration
+   */
+  public <T> T loadResources(Class<T> type, String... resources) {
+    return loadResources(type, Arrays.asList(resources));
+  }
+
+  /**
+   * Loads the given resources using the configuration mapper.
+   *
+   * @param type      the type to load
+   * @param resources the resources to load
+   * @param <T>       the resulting type
+   * @return the loaded configuration
+   */
+  public <T> T loadResources(Class<T> type, List<String> resources) {
+    if (resources == null || resources.isEmpty()) {
+      throw new IllegalArgumentException("resources must be defined");
+    }
+    Config config = null;
+    for (String resource : resources) {
+      if (config == null) {
+        config = ConfigFactory.load(classLoader, resource);
+      } else {
+        config = config.withFallback(ConfigFactory.load(classLoader, resource));
+      }
+    }
+    return map(checkNotNull(config, "config cannot be null").resolve(), type);
+  }
+
+  /**
+   * Applies the given configuration to the given type.
+   *
+   * @param config the configuration to apply
+   * @param clazz  the class to which to apply the configuration
+   */
+  protected <T> T map(Config config, Class<T> clazz) {
+    return map(config, null, null, clazz);
+  }
+
+  protected <T> T newInstance(Config config, String key, Class<T> clazz) {
+    try {
+      return clazz.newInstance();
+    } catch (InstantiationException | IllegalAccessException e) {
+      throw new ConfigurationException(clazz.getName() + " needs a public no-args constructor to be used as a bean", e);
+    }
+  }
+
+  /**
+   * Applies the given configuration to the given type.
+   *
+   * @param config the configuration to apply
+   * @param clazz  the class to which to apply the configuration
+   */
+  @SuppressWarnings("unchecked")
+  protected <T> T map(Config config, String path, String name, Class<T> clazz) {
+    T instance = newInstance(config, name, clazz);
+
+    // Map config property names to bean properties.
+    Map<String, String> propertyNames = new HashMap<>();
+    for (Map.Entry<String, ConfigValue> configProp : config.root().entrySet()) {
+      String originalName = configProp.getKey();
+      String camelName = toCamelCase(originalName);
+      // if a setting is in there both as some hyphen name and the camel name,
+      // the camel one wins
+      if (!propertyNames.containsKey(camelName) || originalName.equals(camelName)) {
+        propertyNames.put(camelName, originalName);
+      }
+    }
+
+    // First use setters and then fall back to fields.
+    mapSetters(instance, clazz, path, name, propertyNames, config);
+    mapFields(instance, clazz, path, name, propertyNames, config);
+
+    // If any properties present in the configuration were not found on config beans, throw an exception.
+    if (!propertyNames.isEmpty()) {
+      checkRemainingProperties(propertyNames.keySet(), describeProperties(instance), toPath(path, name), clazz);
+    }
+    return instance;
+  }
+
+  protected void checkRemainingProperties(Set<String> missingProperties, List<String> availableProperties, String path, Class<?> clazz) {
+    Properties properties = System.getProperties();
+    List<String> cleanNames = missingProperties.stream()
+        .map(propertyName -> toPath(path, propertyName))
+        .filter(propertyName -> !properties.containsKey(propertyName))
+        .filter(propertyName -> properties.entrySet().stream().noneMatch(entry -> entry.getKey().toString().startsWith(propertyName + ".")))
+        .sorted()
+        .collect(Collectors.toList());
+    if (!cleanNames.isEmpty()) {
+      throw new ConfigurationException("Unknown properties present in configuration: " + Joiner.on(", ").join(cleanNames) + "\n"
+          + "Available properties:\n- " + Joiner.on("\n- ").join(availableProperties));
+    }
+  }
+
+  private List<String> describeProperties(Object instance) {
+    Stream<String> setters = getSetterDescriptors(instance.getClass())
+        .stream()
+        .map(descriptor -> descriptor.name);
+    Stream<String> fields = getFieldDescriptors(instance.getClass())
+        .stream()
+        .map(descriptor -> descriptor.name);
+    return Stream.concat(setters, fields)
+        .sorted()
+        .collect(Collectors.toList());
+  }
+
+  private <T> void mapSetters(T instance, Class<T> clazz, String path, String name, Map<String, String> propertyNames, Config config) {
+    try {
+      for (SetterDescriptor descriptor : getSetterDescriptors(instance.getClass())) {
+        Method setter = descriptor.setter;
+        Type parameterType = setter.getGenericParameterTypes()[0];
+        Class<?> parameterClass = setter.getParameterTypes()[0];
+
+        String configPropName = propertyNames.remove(descriptor.name);
+        if (configPropName == null) {
+          if ((Named.class.isAssignableFrom(clazz) || NamedConfig.class.isAssignableFrom(clazz))
+              && descriptor.setter.getParameterTypes()[0] == String.class && name != null && "name".equals(descriptor.name)) {
+            if (descriptor.deprecated) {
+              if (path == null) {
+                LOGGER.warn("{} is deprecated!", name);
+              } else {
+                LOGGER.warn("{}.{} is deprecated!", path, name);
+              }
+            }
+            setter.invoke(instance, name);
+          }
+          continue;
+        }
+
+        Object value = getValue(instance.getClass(), parameterType, parameterClass, config, toPath(path, name), configPropName);
+        if (value != null) {
+          if (descriptor.deprecated) {
+            if (path == null) {
+              LOGGER.warn("{}.{} is deprecated!", name, configPropName);
+            } else {
+              LOGGER.warn("{}.{}.{} is deprecated!", path, name, configPropName);
+            }
+          }
+          setter.invoke(instance, value);
+        }
+      }
+    } catch (IllegalAccessException e) {
+      throw new ConfigurationException(instance.getClass().getName() + " getters and setters are not accessible, they must be for use as a bean", e);
+    } catch (InvocationTargetException e) {
+      throw new ConfigurationException("Calling bean method on " + instance.getClass().getName() + " caused an exception", e);
+    }
+  }
+
+  private <T> void mapFields(T instance, Class<T> clazz, String path, String name, Map<String, String> propertyNames, Config config) {
+    try {
+      for (FieldDescriptor descriptor : getFieldDescriptors(instance.getClass())) {
+        Field field = descriptor.field;
+        field.setAccessible(true);
+
+        Type genericType = field.getGenericType();
+        Class<?> fieldClass = field.getType();
+
+        String configPropName = propertyNames.remove(descriptor.name);
+        if (configPropName == null) {
+          if (Named.class.isAssignableFrom(clazz) && field.getType() == String.class && name != null && "name".equals(descriptor.name)) {
+            if (descriptor.deprecated) {
+              LOGGER.warn("{}.{} is deprecated!", path, name);
+            }
+            field.set(instance, name);
+          }
+          continue;
+        }
+
+        Object value = getValue(instance.getClass(), genericType, fieldClass, config, toPath(path, name), configPropName);
+        if (value != null) {
+          if (descriptor.deprecated) {
+            LOGGER.warn("{}.{} is deprecated!", path, name);
+          }
+          field.set(instance, value);
+        }
+      }
+    } catch (IllegalAccessException e) {
+      throw new ConfigurationException(instance.getClass().getName() + " fields are not accessible, they must be for use as a bean", e);
+    }
+  }
+
+  protected Object getValue(Class<?> beanClass, Type parameterType, Class<?> parameterClass, Config config, String configPath, String configPropName) {
+    if (parameterClass == Boolean.class || parameterClass == boolean.class) {
+      try {
+        return config.getBoolean(configPropName);
+      } catch (ConfigException.WrongType e) {
+        return Boolean.parseBoolean(config.getString(configPropName));
+      }
+    } else if (parameterClass == Integer.class || parameterClass == int.class) {
+      try {
+        return config.getInt(configPropName);
+      } catch (ConfigException.WrongType e) {
+        try {
+          return Integer.parseInt(config.getString(configPropName));
+        } catch (NumberFormatException e1) {
+          throw e;
+        }
+      }
+    } else if (parameterClass == Double.class || parameterClass == double.class) {
+      try {
+        return config.getDouble(configPropName);
+      } catch (ConfigException.WrongType e) {
+        try {
+          return Double.parseDouble(config.getString(configPropName));
+        } catch (NumberFormatException e1) {
+          throw e;
+        }
+      }
+    } else if (parameterClass == Long.class || parameterClass == long.class) {
+      try {
+        return config.getLong(configPropName);
+      } catch (ConfigException.WrongType e) {
+        try {
+          return Long.parseLong(config.getString(configPropName));
+        } catch (NumberFormatException e1) {
+          throw e;
+        }
+      }
+    } else if (parameterClass == String.class) {
+      return config.getString(configPropName);
+    } else if (parameterClass == Duration.class) {
+      return config.getDuration(configPropName);
+    } else if (parameterClass == MemorySize.class) {
+      ConfigMemorySize size = config.getMemorySize(configPropName);
+      return new MemorySize(size.toBytes());
+    } else if (parameterClass == Object.class) {
+      return config.getAnyRef(configPropName);
+    } else if (parameterClass == List.class || parameterClass == Collection.class) {
+      return getListValue(beanClass, parameterType, parameterClass, config, configPath, configPropName);
+    } else if (parameterClass == Set.class) {
+      return getSetValue(beanClass, parameterType, parameterClass, config, configPath, configPropName);
+    } else if (parameterClass == Map.class) {
+      return getMapValue(beanClass, parameterType, parameterClass, config, configPath, configPropName);
+    } else if (parameterClass == Config.class) {
+      return config.getConfig(configPropName);
+    } else if (parameterClass == ConfigObject.class) {
+      return config.getObject(configPropName);
+    } else if (parameterClass == ConfigValue.class) {
+      return config.getValue(configPropName);
+    } else if (parameterClass == ConfigList.class) {
+      return config.getList(configPropName);
+    } else if (parameterClass == Class.class) {
+      String className = config.getString(configPropName);
+      try {
+        return classLoader.loadClass(className);
+      } catch (ClassNotFoundException e) {
+        throw new ConfigurationException("Failed to load class: " + className);
+      }
+    } else if (parameterClass.isEnum()) {
+      String value = config.getString(configPropName);
+      String enumName = value.replace("-", "_").toUpperCase();
+      @SuppressWarnings("unchecked")
+      Enum enumValue = Enum.valueOf((Class<Enum>) parameterClass, enumName);
+      try {
+        Deprecated deprecated = enumValue.getDeclaringClass().getField(enumName).getAnnotation(Deprecated.class);
+        if (deprecated != null) {
+          LOGGER.warn("{}.{} = {} is deprecated!", configPath, configPropName, value);
+        }
+      } catch (NoSuchFieldException e) {
+      }
+      return enumValue;
+    } else {
+      return map(config.getConfig(configPropName), configPath, configPropName, parameterClass);
+    }
+  }
+
+  protected Map getMapValue(Class<?> beanClass, Type parameterType, Class<?> parameterClass, Config config, String configPath, String configPropName) {
+    Type[] typeArgs = ((ParameterizedType) parameterType).getActualTypeArguments();
+    Type keyType = typeArgs[0];
+    Type valueType = typeArgs[1];
+
+    Map<Object, Object> map = new HashMap<>();
+    Config childConfig = config.getConfig(configPropName);
+    Class valueClass = (Class) (valueType instanceof ParameterizedType ? ((ParameterizedType) valueType).getRawType() : valueType);
+    for (String key : config.getObject(configPropName).unwrapped().keySet()) {
+      Object value = getValue(Map.class, valueType, valueClass, childConfig, toPath(configPath, configPropName), key);
+      map.put(getKeyValue(keyType, key), value);
+    }
+    return map;
+  }
+
+  protected Object getKeyValue(Type keyType, String key) {
+    if (keyType == Boolean.class || keyType == boolean.class) {
+      return Boolean.parseBoolean(key);
+    } else if (keyType == Integer.class || keyType == int.class) {
+      return Integer.parseInt(key);
+    } else if (keyType == Double.class || keyType == double.class) {
+      return Double.parseDouble(key);
+    } else if (keyType == Long.class || keyType == long.class) {
+      return Long.parseLong(key);
+    } else if (keyType == String.class) {
+      return key;
+    } else {
+      throw new ConfigurationException("Invalid map key type: " + keyType);
+    }
+  }
+
+  protected Object getSetValue(Class<?> beanClass, Type parameterType, Class<?> parameterClass, Config config, String configPath, String configPropName) {
+    return new HashSet((List) getListValue(beanClass, parameterType, parameterClass, config, configPath, configPropName));
+  }
+
+  protected Object getListValue(Class<?> beanClass, Type parameterType, Class<?> parameterClass, Config config, String configPath, String configPropName) {
+    Type elementType = ((ParameterizedType) parameterType).getActualTypeArguments()[0];
+    if (elementType instanceof ParameterizedType) {
+      elementType = ((ParameterizedType) elementType).getRawType();
+    }
+
+    if (elementType == Boolean.class) {
+      try {
+        return config.getBooleanList(configPropName);
+      } catch (ConfigException.WrongType e) {
+        return config.getStringList(configPropName)
+            .stream()
+            .map(Boolean::parseBoolean)
+            .collect(Collectors.toList());
+      }
+    } else if (elementType == Integer.class) {
+      try {
+        return config.getIntList(configPropName);
+      } catch (ConfigException.WrongType e) {
+        return config.getStringList(configPropName)
+            .stream()
+            .map(value -> {
+              try {
+                return Integer.parseInt(value);
+              } catch (NumberFormatException e2) {
+                throw e;
+              }
+            }).collect(Collectors.toList());
+      }
+    } else if (elementType == Double.class) {
+      try {
+        return config.getDoubleList(configPropName);
+      } catch (ConfigException.WrongType e) {
+        return config.getStringList(configPropName)
+            .stream()
+            .map(value -> {
+              try {
+                return Double.parseDouble(value);
+              } catch (NumberFormatException e2) {
+                throw e;
+              }
+            }).collect(Collectors.toList());
+      }
+    } else if (elementType == Long.class) {
+      try {
+        return config.getLongList(configPropName);
+      } catch (ConfigException.WrongType e) {
+        return config.getStringList(configPropName)
+            .stream()
+            .map(value -> {
+              try {
+                return Long.parseLong(value);
+              } catch (NumberFormatException e2) {
+                throw e;
+              }
+            }).collect(Collectors.toList());
+      }
+    } else if (elementType == String.class) {
+      return config.getStringList(configPropName);
+    } else if (elementType == Duration.class) {
+      return config.getDurationList(configPropName);
+    } else if (elementType == MemorySize.class) {
+      List<ConfigMemorySize> sizes = config.getMemorySizeList(configPropName);
+      return sizes.stream()
+          .map(size -> new MemorySize(size.toBytes()))
+          .collect(Collectors.toList());
+    } else if (elementType == Class.class) {
+      return config.getStringList(configPropName)
+          .stream()
+          .map(className -> {
+            try {
+              return classLoader.loadClass(className);
+            } catch (ClassNotFoundException e) {
+              throw new ConfigurationException("Failed to load class: " + className);
+            }
+          })
+          .collect(Collectors.toList());
+    } else if (elementType == Object.class) {
+      return config.getAnyRefList(configPropName);
+    } else if (((Class<?>) elementType).isEnum()) {
+      @SuppressWarnings("unchecked")
+      List<Enum> enumValues = config.getEnumList((Class<Enum>) elementType, configPropName);
+      return enumValues;
+    } else {
+      List<Object> beanList = new ArrayList<>();
+      List<? extends Config> configList = config.getConfigList(configPropName);
+      int i = 0;
+      for (Config listMember : configList) {
+        beanList.add(map(listMember, toPath(configPath, configPropName), String.valueOf(i), (Class<?>) elementType));
+      }
+      return beanList;
+    }
+  }
+
+  protected String toPath(String path, String name) {
+    return path != null ? String.format("%s.%s", path, name) : name;
+  }
+
+  protected static boolean isSimpleType(Class<?> parameterClass) {
+    return parameterClass == Boolean.class || parameterClass == boolean.class
+        || parameterClass == Integer.class || parameterClass == int.class
+        || parameterClass == Double.class || parameterClass == double.class
+        || parameterClass == Long.class || parameterClass == long.class
+        || parameterClass == String.class
+        || parameterClass == Duration.class
+        || parameterClass == MemorySize.class
+        || parameterClass == List.class
+        || parameterClass == Map.class
+        || parameterClass == Class.class;
+  }
+
+  protected static String toCamelCase(String originalName) {
+    String[] words = originalName.split("-+");
+    if (words.length > 1) {
+      LOGGER.warn("Kebab case config name '" + originalName + "' is deprecated!");
+      StringBuilder nameBuilder = new StringBuilder(originalName.length());
+      for (String word : words) {
+        if (nameBuilder.length() == 0) {
+          nameBuilder.append(word);
+        } else {
+          nameBuilder.append(word.substring(0, 1).toUpperCase());
+          nameBuilder.append(word.substring(1));
+        }
+      }
+      return nameBuilder.toString();
+    }
+    return originalName;
+  }
+
+  protected static String toSetterName(String name) {
+    return "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
+  }
+
+  protected static Collection<SetterDescriptor> getSetterDescriptors(Class<?> clazz) {
+    Map<String, SetterDescriptor> descriptors = Maps.newHashMap();
+    for (Method method : clazz.getMethods()) {
+      String name = method.getName();
+      if (method.getParameterTypes().length == 1
+          && name.length() > 3
+          && "set".equals(name.substring(0, 3))
+          && name.charAt(3) >= 'A'
+          && name.charAt(3) <= 'Z') {
+
+        // Strip the "set" prefix from the property name.
+        name = method.getName().substring(3);
+        name = name.length() > 1
+            ? name.substring(0, 1).toLowerCase() + name.substring(1)
+            : name.toLowerCase();
+
+        // Strip the "Config" suffix from the property name.
+        if (name.endsWith("Config")) {
+          name = name.substring(0, name.length() - "Config".length());
+        }
+
+        // If a setter with this property name has already been registered, determine whether to override it.
+        // We favor simpler types over more complex types (i.e. beans).
+        SetterDescriptor descriptor = descriptors.get(name);
+        if (descriptor != null) {
+          Class<?> type = method.getParameterTypes()[0];
+          if (isSimpleType(type)) {
+            descriptors.put(name, new SetterDescriptor(name, method));
+          }
+        } else {
+          descriptors.put(name, new SetterDescriptor(name, method));
+        }
+      }
+    }
+    return descriptors.values();
+  }
+
+  protected static Collection<FieldDescriptor> getFieldDescriptors(Class<?> type) {
+    Class<?> clazz = type;
+    Map<String, FieldDescriptor> descriptors = Maps.newHashMap();
+    while (clazz != Object.class) {
+      for (Field field : clazz.getDeclaredFields()) {
+        // If the field is static or transient, ignore it.
+        if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
+          continue;
+        }
+
+        // If the field has a setter, ignore it and use the setter.
+        Method method = Stream.of(clazz.getMethods())
+            .filter(m -> m.getName().equals(toSetterName(field.getName())))
+            .findFirst()
+            .orElse(null);
+        if (method != null) {
+          continue;
+        }
+
+        // Strip the "Config" suffix from the field.
+        String name = field.getName();
+        if (name.endsWith("Config")) {
+          name = name.substring(0, name.length() - "Config".length());
+        }
+        descriptors.putIfAbsent(name, new FieldDescriptor(name, field));
+      }
+      clazz = clazz.getSuperclass();
+    }
+    return Lists.newArrayList(descriptors.values());
+  }
+
+  protected static class SetterDescriptor {
+    private final String name;
+    private final Method setter;
+    private final boolean deprecated;
+
+    SetterDescriptor(String name, Method setter) {
+      this.name = name;
+      this.setter = setter;
+      this.deprecated = setter.getAnnotation(Deprecated.class) != null;
+    }
+  }
+
+  protected static class FieldDescriptor {
+    private final String name;
+    private final Field field;
+    private final boolean deprecated;
+
+    FieldDescriptor(String name, Field field) {
+      this.name = name;
+      this.field = field;
+      this.deprecated = field.getAnnotation(Deprecated.class) != null;
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/config/ConfigurationException.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/config/ConfigurationException.java
new file mode 100644 (file)
index 0000000..6a83d3c
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.config;
+
+import io.atomix.utils.AtomixRuntimeException;
+
+/**
+ * Atomix configuration exception.
+ */
+public class ConfigurationException extends AtomixRuntimeException {
+  public ConfigurationException(String message) {
+    super(message);
+  }
+
+  public ConfigurationException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/config/Configured.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/config/Configured.java
new file mode 100644 (file)
index 0000000..9c04a03
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.config;
+
+/**
+ * Interface for objects configured via a configuration object.
+ */
+public interface Configured<T extends Config> {
+
+  /**
+   * Returns the object configuration.
+   *
+   * @return the object configuration
+   */
+  T config();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/config/NamedConfig.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/config/NamedConfig.java
new file mode 100644 (file)
index 0000000..fd9d1a9
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.config;
+
+/**
+ * Named configuration.
+ */
+public interface NamedConfig<C extends NamedConfig<C>> extends Config {
+
+  /**
+   * Returns the configuration name.
+   *
+   * @return the configuration name
+   */
+  String getName();
+
+  /**
+   * Sets the configuration name.
+   *
+   * @param name the configuration name
+   * @return the configuration object
+   */
+  C setName(String name);
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/config/TypedConfig.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/config/TypedConfig.java
new file mode 100644 (file)
index 0000000..75133d6
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.config;
+
+/**
+ * Typed configuration.
+ */
+public interface TypedConfig<T> extends Config {
+
+  /**
+   * Returns the type name.
+   *
+   * @return the type name
+   */
+  T getType();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/config/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/config/package-info.java
new file mode 100644 (file)
index 0000000..6704c2f
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides classes and interfaces for reading and mapping configuration files.
+ */
+package io.atomix.utils.config;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/event/AbstractEvent.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/event/AbstractEvent.java
new file mode 100644 (file)
index 0000000..cd34430
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.event;
+
+import io.atomix.utils.misc.TimestampPrinter;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Base event implementation.
+ */
+public class AbstractEvent<T extends Enum, S> implements Event<T, S> {
+  private final long time;
+  private final T type;
+  private final S subject;
+
+  /**
+   * Creates an event of a given type and for the specified subject and the
+   * current time.
+   *
+   * @param type    event type
+   * @param subject event subject
+   */
+  protected AbstractEvent(T type, S subject) {
+    this(type, subject, System.currentTimeMillis());
+  }
+
+  /**
+   * Creates an event of a given type and for the specified subject and time.
+   *
+   * @param type    event type
+   * @param subject event subject
+   * @param time    occurrence time
+   */
+  protected AbstractEvent(T type, S subject, long time) {
+    this.type = type;
+    this.subject = subject;
+    this.time = time;
+  }
+
+  @Override
+  public long time() {
+    return time;
+  }
+
+  @Override
+  public T type() {
+    return type;
+  }
+
+  @Override
+  public S subject() {
+    return subject;
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("time", new TimestampPrinter(time))
+        .add("type", type())
+        .add("subject", subject())
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/event/AbstractListenerManager.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/event/AbstractListenerManager.java
new file mode 100644 (file)
index 0000000..55d9626
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.event;
+
+/**
+ * Basis for components which need to export listener mechanism.
+ */
+public abstract class AbstractListenerManager<E extends Event, L extends EventListener<E>> implements ListenerService<E, L> {
+
+  protected final ListenerRegistry<E, L> listenerRegistry = new ListenerRegistry<>();
+
+  @Override
+  public void addListener(L listener) {
+    listenerRegistry.addListener(listener);
+  }
+
+  @Override
+  public void removeListener(L listener) {
+    listenerRegistry.removeListener(listener);
+  }
+
+  /**
+   * Posts the specified event to the local event dispatcher.
+   *
+   * @param event event to be posted; may be null
+   */
+  protected void post(E event) {
+    listenerRegistry.process(event);
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/event/Event.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/event/Event.java
new file mode 100644 (file)
index 0000000..2484900
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.event;
+
+/**
+ * Abstraction of an of a time-stamped event pertaining to an arbitrary subject.
+ */
+public interface Event<T, S> {
+
+  /**
+   * Returns the timestamp of when the event occurred, given in milliseconds
+   * since the start of epoch.
+   *
+   * @return timestamp in milliseconds
+   */
+  long time();
+
+  /**
+   * Returns the type of the event.
+   *
+   * @return event type
+   */
+  T type();
+
+  /**
+   * Returns the subject of the event.
+   *
+   * @return subject to which this event pertains
+   */
+  S subject();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/event/EventFilter.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/event/EventFilter.java
new file mode 100644 (file)
index 0000000..fa38b72
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.event;
+
+/**
+ * Entity capable of filtering events.
+ */
+public interface EventFilter<E extends Event> {
+
+  /**
+   * Indicates whether the specified event is of interest or not.
+   * Default implementation always returns true.
+   *
+   * @param event event to be inspected
+   * @return true if event is relevant; false otherwise
+   */
+  default boolean isRelevant(E event) {
+    return true;
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/event/EventListener.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/event/EventListener.java
new file mode 100644 (file)
index 0000000..6e0e1c7
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.event;
+
+/**
+ * Entity capable of receiving events.
+ */
+@FunctionalInterface
+public interface EventListener<E extends Event> extends EventFilter<E> {
+
+  /**
+   * Reacts to the specified event.
+   *
+   * @param event event to be processed
+   */
+  void event(E event);
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/event/EventSink.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/event/EventSink.java
new file mode 100644 (file)
index 0000000..cbffc9c
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.event;
+
+/**
+ * Abstraction of an event sink capable of processing the specified event types.
+ */
+public interface EventSink<E extends Event> {
+
+  /**
+   * Processes the specified event.
+   *
+   * @param event event to be processed
+   */
+  void process(E event);
+
+  /**
+   * Handles notification that event processing time limit has been exceeded.
+   */
+  default void onProcessLimit() {
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/event/ListenerRegistry.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/event/ListenerRegistry.java
new file mode 100644 (file)
index 0000000..757288c
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.event;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Base implementation of an event sink and a registry capable of tracking
+ * listeners and dispatching events to them as part of event sink processing.
+ */
+public class ListenerRegistry<E extends Event, L extends EventListener<E>>
+    implements ListenerService<E, L>, EventSink<E> {
+
+  private static final long LIMIT = 1_800; // ms
+
+  private final Logger log = LoggerFactory.getLogger(getClass());
+
+  private long lastStart;
+  private L lastListener;
+
+  /**
+   * Set of listeners that have registered.
+   */
+  protected final Set<L> listeners = new CopyOnWriteArraySet<>();
+
+  @Override
+  public void addListener(L listener) {
+    checkNotNull(listener, "Listener cannot be null");
+    listeners.add(listener);
+  }
+
+  @Override
+  public void removeListener(L listener) {
+    checkNotNull(listener, "Listener cannot be null");
+    if (!listeners.remove(listener)) {
+      log.warn("Listener {} not registered", listener);
+    }
+  }
+
+  @Override
+  public void process(E event) {
+    for (L listener : listeners) {
+      try {
+        lastListener = listener;
+        lastStart = System.currentTimeMillis();
+        if (listener.isRelevant(event)) {
+          listener.event(event);
+        }
+        lastStart = 0;
+      } catch (Exception error) {
+        reportProblem(event, error);
+      }
+    }
+  }
+
+  @Override
+  public void onProcessLimit() {
+    if (lastStart > 0) {
+      long duration = System.currentTimeMillis() - lastStart;
+      if (duration > LIMIT) {
+        log.error("Listener {} exceeded execution time limit: {} ms; ejected",
+            lastListener.getClass().getName(),
+            duration);
+        removeListener(lastListener);
+      }
+      lastStart = 0;
+    }
+  }
+
+  /**
+   * Reports a problem encountered while processing an event.
+   *
+   * @param event event being processed
+   * @param error error encountered while processing
+   */
+  protected void reportProblem(E event, Throwable error) {
+    log.warn("Exception encountered while processing event " + event, error);
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/event/ListenerService.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/event/ListenerService.java
new file mode 100644 (file)
index 0000000..45db98a
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.event;
+
+/**
+ * Abstraction of a service capable of asynchronously notifying listeners.
+ */
+public interface ListenerService<E extends Event, L extends EventListener<E>> {
+
+  /**
+   * Adds the specified listener.
+   *
+   * @param listener listener to be added
+   */
+  void addListener(L listener);
+
+  /**
+   * Removes the specified listener.
+   *
+   * @param listener listener to be removed
+   */
+  void removeListener(L listener);
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/event/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/event/package-info.java
new file mode 100644 (file)
index 0000000..41952bb
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides classes and interfaces for creating and handling generic events.
+ */
+package io.atomix.utils.event;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/ContextualLogger.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/ContextualLogger.java
new file mode 100644 (file)
index 0000000..7c11ff2
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.logging;
+
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+/**
+ * Contextual logger.
+ */
+public class ContextualLogger extends DelegatingLogger {
+  private static final String SEPARATOR = " - ";
+  private final LoggerContext context;
+
+  public ContextualLogger(Logger delegate, LoggerContext context) {
+    super(delegate);
+    this.context = context;
+  }
+
+  /**
+   * Returns a contextualized version of the given string.
+   *
+   * @param msg the message to contextualize
+   * @return the contextualized message
+   */
+  private String contextualize(String msg) {
+    return context + SEPARATOR + msg;
+  }
+
+  @Override
+  public void trace(String msg) {
+    if (isTraceEnabled()) {
+      super.trace(contextualize(msg));
+    }
+  }
+
+  @Override
+  public void trace(String format, Object arg) {
+    if (isTraceEnabled()) {
+      super.trace(contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void trace(String format, Object arg1, Object arg2) {
+    if (isTraceEnabled()) {
+      super.trace(contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void trace(String format, Object... arguments) {
+    if (isTraceEnabled()) {
+      super.trace(contextualize(format), arguments);
+    }
+  }
+
+  @Override
+  public void trace(String msg, Throwable t) {
+    if (isTraceEnabled()) {
+      super.trace(contextualize(msg), t);
+    }
+  }
+
+  @Override
+  public void trace(Marker marker, String msg) {
+    if (isTraceEnabled()) {
+      super.trace(marker, contextualize(msg));
+    }
+  }
+
+  @Override
+  public void trace(Marker marker, String format, Object arg) {
+    if (isTraceEnabled()) {
+      super.trace(marker, contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void trace(Marker marker, String format, Object arg1, Object arg2) {
+    if (isTraceEnabled()) {
+      super.trace(marker, contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void trace(Marker marker, String format, Object... argArray) {
+    if (isTraceEnabled()) {
+      super.trace(marker, contextualize(format), argArray);
+    }
+  }
+
+  @Override
+  public void trace(Marker marker, String msg, Throwable t) {
+    if (isTraceEnabled()) {
+      super.trace(marker, contextualize(msg), t);
+    }
+  }
+
+  @Override
+  public void debug(String msg) {
+    if (isDebugEnabled()) {
+      super.debug(contextualize(msg));
+    }
+  }
+
+  @Override
+  public void debug(String format, Object arg) {
+    if (isDebugEnabled()) {
+      super.debug(contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void debug(String format, Object arg1, Object arg2) {
+    if (isDebugEnabled()) {
+      super.debug(contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void debug(String format, Object... arguments) {
+    if (isDebugEnabled()) {
+      super.debug(contextualize(format), arguments);
+    }
+  }
+
+  @Override
+  public void debug(String msg, Throwable t) {
+    if (isDebugEnabled()) {
+      super.debug(contextualize(msg), t);
+    }
+  }
+
+  @Override
+  public void debug(Marker marker, String msg) {
+    if (isDebugEnabled()) {
+      super.debug(marker, contextualize(msg));
+    }
+  }
+
+  @Override
+  public void debug(Marker marker, String format, Object arg) {
+    if (isDebugEnabled()) {
+      super.debug(marker, contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void debug(Marker marker, String format, Object arg1, Object arg2) {
+    if (isDebugEnabled()) {
+      super.debug(marker, contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void debug(Marker marker, String format, Object... arguments) {
+    if (isDebugEnabled()) {
+      super.debug(marker, contextualize(format), arguments);
+    }
+  }
+
+  @Override
+  public void debug(Marker marker, String msg, Throwable t) {
+    if (isDebugEnabled()) {
+      super.debug(marker, contextualize(msg), t);
+    }
+  }
+
+  @Override
+  public void info(String msg) {
+    if (isInfoEnabled()) {
+      super.info(contextualize(msg));
+    }
+  }
+
+  @Override
+  public void info(String format, Object arg) {
+    if (isInfoEnabled()) {
+      super.info(contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void info(String format, Object arg1, Object arg2) {
+    if (isInfoEnabled()) {
+      super.info(contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void info(String format, Object... arguments) {
+    if (isInfoEnabled()) {
+      super.info(contextualize(format), arguments);
+    }
+  }
+
+  @Override
+  public void info(String msg, Throwable t) {
+    if (isInfoEnabled()) {
+      super.info(contextualize(msg), t);
+    }
+  }
+
+  @Override
+  public void info(Marker marker, String msg) {
+    if (isInfoEnabled()) {
+      super.info(marker, contextualize(msg));
+    }
+  }
+
+  @Override
+  public void info(Marker marker, String format, Object arg) {
+    if (isInfoEnabled()) {
+      super.info(marker, contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void info(Marker marker, String format, Object arg1, Object arg2) {
+    if (isInfoEnabled()) {
+      super.info(marker, contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void info(Marker marker, String format, Object... arguments) {
+    if (isInfoEnabled()) {
+      super.info(marker, contextualize(format), arguments);
+    }
+  }
+
+  @Override
+  public void info(Marker marker, String msg, Throwable t) {
+    if (isInfoEnabled()) {
+      super.info(marker, contextualize(msg), t);
+    }
+  }
+
+  @Override
+  public void warn(String msg) {
+    if (isWarnEnabled()) {
+      super.warn(contextualize(msg));
+    }
+  }
+
+  @Override
+  public void warn(String format, Object arg) {
+    if (isWarnEnabled()) {
+      super.warn(contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void warn(String format, Object... arguments) {
+    if (isWarnEnabled()) {
+      super.warn(contextualize(format), arguments);
+    }
+  }
+
+  @Override
+  public void warn(String format, Object arg1, Object arg2) {
+    if (isWarnEnabled()) {
+      super.warn(contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void warn(String msg, Throwable t) {
+    if (isWarnEnabled()) {
+      super.warn(contextualize(msg), t);
+    }
+  }
+
+  @Override
+  public void warn(Marker marker, String msg) {
+    if (isWarnEnabled()) {
+      super.warn(marker, contextualize(msg));
+    }
+  }
+
+  @Override
+  public void warn(Marker marker, String format, Object arg) {
+    if (isWarnEnabled()) {
+      super.warn(marker, contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void warn(Marker marker, String format, Object arg1, Object arg2) {
+    if (isWarnEnabled()) {
+      super.warn(marker, contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void warn(Marker marker, String format, Object... arguments) {
+    if (isWarnEnabled()) {
+      super.warn(marker, contextualize(format), arguments);
+    }
+  }
+
+  @Override
+  public void warn(Marker marker, String msg, Throwable t) {
+    if (isWarnEnabled()) {
+      super.warn(marker, contextualize(msg), t);
+    }
+  }
+
+  @Override
+  public void error(String msg) {
+    if (isErrorEnabled()) {
+      super.error(contextualize(msg));
+    }
+  }
+
+  @Override
+  public void error(String format, Object arg) {
+    if (isErrorEnabled()) {
+      super.error(contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void error(String format, Object arg1, Object arg2) {
+    if (isErrorEnabled()) {
+      super.error(contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void error(String format, Object... arguments) {
+    if (isErrorEnabled()) {
+      super.error(contextualize(format), arguments);
+    }
+  }
+
+  @Override
+  public void error(String msg, Throwable t) {
+    if (isErrorEnabled()) {
+      super.error(contextualize(msg), t);
+    }
+  }
+
+  @Override
+  public void error(Marker marker, String msg) {
+    if (isErrorEnabled()) {
+      super.error(marker, contextualize(msg));
+    }
+  }
+
+  @Override
+  public void error(Marker marker, String format, Object arg) {
+    if (isErrorEnabled()) {
+      super.error(marker, contextualize(format), arg);
+    }
+  }
+
+  @Override
+  public void error(Marker marker, String format, Object arg1, Object arg2) {
+    if (isErrorEnabled()) {
+      super.error(marker, contextualize(format), arg1, arg2);
+    }
+  }
+
+  @Override
+  public void error(Marker marker, String format, Object... arguments) {
+    if (isErrorEnabled()) {
+      super.error(marker, contextualize(format), arguments);
+    }
+  }
+
+  @Override
+  public void error(Marker marker, String msg, Throwable t) {
+    if (isErrorEnabled()) {
+      super.error(marker, contextualize(msg), t);
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/ContextualLoggerFactory.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/ContextualLoggerFactory.java
new file mode 100644 (file)
index 0000000..c6e0f13
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.logging;
+
+import org.slf4j.LoggerFactory;
+
+/**
+ * Contextual logger factory.
+ */
+public class ContextualLoggerFactory {
+
+  /**
+   * Returns a contextual logger.
+   *
+   * @param name the contextual logger name
+   * @param context the logger context
+   * @return the logger
+   */
+  public static ContextualLogger getLogger(String name, LoggerContext context) {
+    return new ContextualLogger(LoggerFactory.getLogger(name), context);
+  }
+
+  /**
+   * Returns a contextual logger.
+   *
+   * @param clazz the contextual logger class
+   * @param context the logger context
+   * @return the logger
+   */
+  public static ContextualLogger getLogger(Class clazz, LoggerContext context) {
+    return new ContextualLogger(LoggerFactory.getLogger(clazz), context);
+  }
+
+  private ContextualLoggerFactory() {
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/DelegatingLogger.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/DelegatingLogger.java
new file mode 100644 (file)
index 0000000..09236d2
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.logging;
+
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Delegating logger.
+ */
+public class DelegatingLogger implements Logger {
+  private final Logger delegate;
+
+  public DelegatingLogger(Logger delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public String getName() {
+    return delegate.getName();
+  }
+
+  @Override
+  public boolean isTraceEnabled() {
+    return delegate.isTraceEnabled();
+  }
+
+  @Override
+  public void trace(String msg) {
+    delegate.trace(msg);
+  }
+
+  @Override
+  public void trace(String format, Object arg) {
+    delegate.trace(format, arg);
+  }
+
+  @Override
+  public void trace(String format, Object arg1, Object arg2) {
+    delegate.trace(format, arg1, arg2);
+  }
+
+  @Override
+  public void trace(String format, Object... arguments) {
+    delegate.trace(format, arguments);
+  }
+
+  @Override
+  public void trace(String msg, Throwable t) {
+    delegate.trace(msg, t);
+  }
+
+  @Override
+  public boolean isTraceEnabled(Marker marker) {
+    return delegate.isTraceEnabled(marker);
+  }
+
+  @Override
+  public void trace(Marker marker, String msg) {
+    delegate.trace(marker, msg);
+  }
+
+  @Override
+  public void trace(Marker marker, String format, Object arg) {
+    delegate.trace(marker, format, arg);
+  }
+
+  @Override
+  public void trace(Marker marker, String format, Object arg1, Object arg2) {
+    delegate.trace(marker, format, arg1, arg2);
+  }
+
+  @Override
+  public void trace(Marker marker, String format, Object... argArray) {
+    delegate.trace(marker, format, argArray);
+  }
+
+  @Override
+  public void trace(Marker marker, String msg, Throwable t) {
+    delegate.trace(marker, msg, t);
+  }
+
+  @Override
+  public boolean isDebugEnabled() {
+    return delegate.isDebugEnabled();
+  }
+
+  @Override
+  public void debug(String msg) {
+    delegate.debug(msg);
+  }
+
+  @Override
+  public void debug(String format, Object arg) {
+    delegate.debug(format, arg);
+  }
+
+  @Override
+  public void debug(String format, Object arg1, Object arg2) {
+    delegate.debug(format, arg1, arg2);
+  }
+
+  @Override
+  public void debug(String format, Object... arguments) {
+    delegate.debug(format, arguments);
+  }
+
+  @Override
+  public void debug(String msg, Throwable t) {
+    delegate.debug(msg, t);
+  }
+
+  @Override
+  public boolean isDebugEnabled(Marker marker) {
+    return delegate.isDebugEnabled(marker);
+  }
+
+  @Override
+  public void debug(Marker marker, String msg) {
+    delegate.debug(marker, msg);
+  }
+
+  @Override
+  public void debug(Marker marker, String format, Object arg) {
+    delegate.debug(marker, format, arg);
+  }
+
+  @Override
+  public void debug(Marker marker, String format, Object arg1, Object arg2) {
+    delegate.debug(marker, format, arg1, arg2);
+  }
+
+  @Override
+  public void debug(Marker marker, String format, Object... arguments) {
+    delegate.debug(marker, format, arguments);
+  }
+
+  @Override
+  public void debug(Marker marker, String msg, Throwable t) {
+    delegate.debug(marker, msg, t);
+  }
+
+  @Override
+  public boolean isInfoEnabled() {
+    return delegate.isInfoEnabled();
+  }
+
+  @Override
+  public void info(String msg) {
+    delegate.info(msg);
+  }
+
+  @Override
+  public void info(String format, Object arg) {
+    delegate.info(format, arg);
+  }
+
+  @Override
+  public void info(String format, Object arg1, Object arg2) {
+    delegate.info(format, arg1, arg2);
+  }
+
+  @Override
+  public void info(String format, Object... arguments) {
+    delegate.info(format, arguments);
+  }
+
+  @Override
+  public void info(String msg, Throwable t) {
+    delegate.info(msg, t);
+  }
+
+  @Override
+  public boolean isInfoEnabled(Marker marker) {
+    return delegate.isInfoEnabled(marker);
+  }
+
+  @Override
+  public void info(Marker marker, String msg) {
+    delegate.info(marker, msg);
+  }
+
+  @Override
+  public void info(Marker marker, String format, Object arg) {
+    delegate.info(marker, format, arg);
+  }
+
+  @Override
+  public void info(Marker marker, String format, Object arg1, Object arg2) {
+    delegate.info(marker, format, arg1, arg2);
+  }
+
+  @Override
+  public void info(Marker marker, String format, Object... arguments) {
+    delegate.info(marker, format, arguments);
+  }
+
+  @Override
+  public void info(Marker marker, String msg, Throwable t) {
+    delegate.info(marker, msg, t);
+  }
+
+  @Override
+  public boolean isWarnEnabled() {
+    return delegate.isWarnEnabled();
+  }
+
+  @Override
+  public void warn(String msg) {
+    delegate.warn(msg);
+  }
+
+  @Override
+  public void warn(String format, Object arg) {
+    delegate.warn(format, arg);
+  }
+
+  @Override
+  public void warn(String format, Object... arguments) {
+    delegate.warn(format, arguments);
+  }
+
+  @Override
+  public void warn(String format, Object arg1, Object arg2) {
+    delegate.warn(format, arg1, arg2);
+  }
+
+  @Override
+  public void warn(String msg, Throwable t) {
+    delegate.warn(msg, t);
+  }
+
+  @Override
+  public boolean isWarnEnabled(Marker marker) {
+    return delegate.isWarnEnabled(marker);
+  }
+
+  @Override
+  public void warn(Marker marker, String msg) {
+    delegate.warn(marker, msg);
+  }
+
+  @Override
+  public void warn(Marker marker, String format, Object arg) {
+    delegate.warn(marker, format, arg);
+  }
+
+  @Override
+  public void warn(Marker marker, String format, Object arg1, Object arg2) {
+    delegate.warn(marker, format, arg1, arg2);
+  }
+
+  @Override
+  public void warn(Marker marker, String format, Object... arguments) {
+    delegate.warn(marker, format, arguments);
+  }
+
+  @Override
+  public void warn(Marker marker, String msg, Throwable t) {
+    delegate.warn(marker, msg, t);
+  }
+
+  @Override
+  public boolean isErrorEnabled() {
+    return delegate.isErrorEnabled();
+  }
+
+  @Override
+  public void error(String msg) {
+    delegate.error(msg);
+  }
+
+  @Override
+  public void error(String format, Object arg) {
+    delegate.error(format, arg);
+  }
+
+  @Override
+  public void error(String format, Object arg1, Object arg2) {
+    delegate.error(format, arg1, arg2);
+  }
+
+  @Override
+  public void error(String format, Object... arguments) {
+    delegate.error(format, arguments);
+  }
+
+  @Override
+  public void error(String msg, Throwable t) {
+    delegate.error(msg, t);
+  }
+
+  @Override
+  public boolean isErrorEnabled(Marker marker) {
+    return delegate.isErrorEnabled(marker);
+  }
+
+  @Override
+  public void error(Marker marker, String msg) {
+    delegate.error(marker, msg);
+  }
+
+  @Override
+  public void error(Marker marker, String format, Object arg) {
+    delegate.error(marker, format, arg);
+  }
+
+  @Override
+  public void error(Marker marker, String format, Object arg1, Object arg2) {
+    delegate.error(marker, format, arg1, arg2);
+  }
+
+  @Override
+  public void error(Marker marker, String format, Object... arguments) {
+    delegate.error(marker, format, arguments);
+  }
+
+  @Override
+  public void error(Marker marker, String msg, Throwable t) {
+    delegate.error(marker, msg, t);
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .addValue(delegate)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/LoggerContext.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/LoggerContext.java
new file mode 100644 (file)
index 0000000..1ba99e6
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.logging;
+
+import com.google.common.base.MoreObjects;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+import java.util.function.Supplier;
+
+/**
+ * Logger context.
+ */
+public class LoggerContext {
+
+  /**
+   * Returns a new contextual logger builder.
+   *
+   * @param name the logger name
+   * @return the logger builder
+   */
+  public static Builder builder(String name) {
+    return new Builder(name);
+  }
+
+  /**
+   * Returns a new contextual logger builder.
+   *
+   * @param clazz the logger class
+   * @return the logger builder
+   */
+  public static Builder builder(Class clazz) {
+    return new Builder(clazz.getSimpleName());
+  }
+
+  private final Supplier<String> stringProvider;
+
+  public LoggerContext(Supplier<String> stringProvider) {
+    this.stringProvider = stringProvider;
+  }
+
+  @Override
+  public String toString() {
+    return stringProvider.get();
+  }
+
+  /**
+   * Contextual logger builder.
+   */
+  public static class Builder implements io.atomix.utils.Builder<LoggerContext> {
+    private final MoreObjects.ToStringHelper identityStringHelper;
+    private MoreObjects.ToStringHelper argsStringHelper;
+    private boolean omitNullValues = false;
+
+    public Builder(String name) {
+      this.identityStringHelper = MoreObjects.toStringHelper(name);
+    }
+
+    /**
+     * Initializes the arguments string helper.
+     */
+    private void initializeArgs() {
+      if (argsStringHelper == null) {
+        argsStringHelper = MoreObjects.toStringHelper("");
+      }
+    }
+
+    /**
+     * Configures the {@link MoreObjects.ToStringHelper} so {@link #toString()} will ignore properties with null
+     * value. The order of calling this method, relative to the {@code add()}/{@code addValue()}
+     * methods, is not significant.
+     */
+    @CanIgnoreReturnValue
+    public Builder omitNullValues() {
+      this.omitNullValues = true;
+      return this;
+    }
+
+    /**
+     * Adds a name/value pair to the formatted output in {@code name=value} format. If {@code value}
+     * is {@code null}, the string {@code "null"} is used, unless {@link #omitNullValues()} is
+     * called, in which case this name/value pair will not be added.
+     */
+    @CanIgnoreReturnValue
+    public Builder add(String name, Object value) {
+      initializeArgs();
+      argsStringHelper.add(name, value);
+      return this;
+    }
+
+    /**
+     * Adds a name/value pair to the formatted output in {@code name=value} format.
+     */
+    @CanIgnoreReturnValue
+    public Builder add(String name, boolean value) {
+      initializeArgs();
+      argsStringHelper.add(name, value);
+      return this;
+    }
+
+    /**
+     * Adds a name/value pair to the formatted output in {@code name=value} format.
+     */
+    @CanIgnoreReturnValue
+    public Builder add(String name, char value) {
+      initializeArgs();
+      argsStringHelper.add(name, value);
+      return this;
+    }
+
+    /**
+     * Adds a name/value pair to the formatted output in {@code name=value} format.
+     */
+    @CanIgnoreReturnValue
+    public Builder add(String name, double value) {
+      initializeArgs();
+      argsStringHelper.add(name, value);
+      return this;
+    }
+
+    /**
+     * Adds a name/value pair to the formatted output in {@code name=value} format.
+     */
+    @CanIgnoreReturnValue
+    public Builder add(String name, float value) {
+      initializeArgs();
+      argsStringHelper.add(name, value);
+      return this;
+    }
+
+    /**
+     * Adds a name/value pair to the formatted output in {@code name=value} format.
+     */
+    @CanIgnoreReturnValue
+    public Builder add(String name, int value) {
+      initializeArgs();
+      argsStringHelper.add(name, value);
+      return this;
+    }
+
+    /**
+     * Adds a name/value pair to the formatted output in {@code name=value} format.
+     */
+    @CanIgnoreReturnValue
+    public Builder add(String name, long value) {
+      initializeArgs();
+      argsStringHelper.add(name, value);
+      return this;
+    }
+
+    /**
+     * Adds an unnamed value to the formatted output.
+     *
+     * <p>It is strongly encouraged to use {@link #add(String, Object)} instead and give value a
+     * readable name.
+     */
+    @CanIgnoreReturnValue
+    public Builder addValue(Object value) {
+      identityStringHelper.addValue(value);
+      return this;
+    }
+
+    /**
+     * Adds an unnamed value to the formatted output.
+     *
+     * <p>It is strongly encouraged to use {@link #add(String, boolean)} instead and give value a
+     * readable name.
+     */
+    @CanIgnoreReturnValue
+    public Builder addValue(boolean value) {
+      identityStringHelper.addValue(value);
+      return this;
+    }
+
+    /**
+     * Adds an unnamed value to the formatted output.
+     *
+     * <p>It is strongly encouraged to use {@link #add(String, char)} instead and give value a
+     * readable name.
+     */
+    @CanIgnoreReturnValue
+    public Builder addValue(char value) {
+      identityStringHelper.addValue(value);
+      return this;
+    }
+
+    /**
+     * Adds an unnamed value to the formatted output.
+     *
+     * <p>It is strongly encouraged to use {@link #add(String, double)} instead and give value a
+     * readable name.
+     */
+    @CanIgnoreReturnValue
+    public Builder addValue(double value) {
+      identityStringHelper.addValue(value);
+      return this;
+    }
+
+    /**
+     * Adds an unnamed value to the formatted output.
+     *
+     * <p>It is strongly encouraged to use {@link #add(String, float)} instead and give value a
+     * readable name.
+     */
+    @CanIgnoreReturnValue
+    public Builder addValue(float value) {
+      identityStringHelper.addValue(value);
+      return this;
+    }
+
+    /**
+     * Adds an unnamed value to the formatted output.
+     *
+     * <p>It is strongly encouraged to use {@link #add(String, int)} instead and give value a
+     * readable name.
+     */
+    @CanIgnoreReturnValue
+    public Builder addValue(int value) {
+      identityStringHelper.addValue(value);
+      return this;
+    }
+
+    /**
+     * Adds an unnamed value to the formatted output.
+     *
+     * <p>It is strongly encouraged to use {@link #add(String, long)} instead and give value a
+     * readable name.
+     */
+    @CanIgnoreReturnValue
+    public Builder addValue(long value) {
+      identityStringHelper.addValue(value);
+      return this;
+    }
+
+    @Override
+    public LoggerContext build() {
+      MoreObjects.ToStringHelper identityStringHelper = this.identityStringHelper;
+      MoreObjects.ToStringHelper argsStringHelper = this.argsStringHelper;
+      if (omitNullValues) {
+        identityStringHelper.omitNullValues();
+        if (argsStringHelper != null) {
+          argsStringHelper.omitNullValues();
+        }
+      }
+      return new LoggerContext(() -> {
+        if (argsStringHelper == null) {
+          return identityStringHelper.toString();
+        } else {
+          return identityStringHelper.toString() + argsStringHelper.toString();
+        }
+      });
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/logging/package-info.java
new file mode 100644 (file)
index 0000000..823cde2
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides utility classes for logging in complex objects.
+ */
+package io.atomix.utils.logging;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/BufferCleaner.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/BufferCleaner.java
new file mode 100644 (file)
index 0000000..120c559
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.memory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Objects;
+
+import static java.lang.invoke.MethodHandles.constant;
+import static java.lang.invoke.MethodHandles.dropArguments;
+import static java.lang.invoke.MethodHandles.filterReturnValue;
+import static java.lang.invoke.MethodHandles.guardWithTest;
+import static java.lang.invoke.MethodHandles.lookup;
+import static java.lang.invoke.MethodType.methodType;
+
+/**
+ * Utility class which allows explicit calls to the DirectByteBuffer cleaner method instead of relying on GC.
+ */
+public class BufferCleaner {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(BufferCleaner.class);
+
+  /**
+   * Reference to a Cleaner that does unmapping; no-op if not supported.
+   */
+  private static final Cleaner CLEANER;
+
+  static {
+    final Object hack = AccessController.doPrivileged((PrivilegedAction<Object>) BufferCleaner::unmapHackImpl);
+    if (hack instanceof Cleaner) {
+      CLEANER = (Cleaner) hack;
+      LOGGER.debug("java.nio.DirectByteBuffer.cleaner(): available");
+    } else {
+      CLEANER = (ByteBuffer buffer) -> {
+        // noop
+      };
+      LOGGER.debug("java.nio.DirectByteBuffer.cleaner(): unavailable", hack);
+    }
+  }
+
+  private static Object unmapHackImpl() {
+    final MethodHandles.Lookup lookup = lookup();
+    try {
+      try {
+        // *** sun.misc.Unsafe unmapping (Java 9+) ***
+        final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+        // first check if Unsafe has the right method, otherwise we can give up
+        // without doing any security critical stuff:
+        final MethodHandle unmapper = lookup.findVirtual(unsafeClass, "invokeCleaner",
+            methodType(void.class, ByteBuffer.class));
+        // fetch the unsafe instance and bind it to the virtual MH:
+        final Field f = unsafeClass.getDeclaredField("theUnsafe");
+        f.setAccessible(true);
+        final Object theUnsafe = f.get(null);
+        return newBufferCleaner(ByteBuffer.class, unmapper.bindTo(theUnsafe));
+      } catch (SecurityException se) {
+        // rethrow to report errors correctly (we need to catch it here, as we also catch RuntimeException below!):
+        throw se;
+      } catch (ReflectiveOperationException | RuntimeException e) {
+        // *** sun.misc.Cleaner unmapping (Java 8) ***
+        final Class<?> directBufferClass = Class.forName("java.nio.DirectByteBuffer");
+
+        final Method m = directBufferClass.getMethod("cleaner");
+        m.setAccessible(true);
+        final MethodHandle directBufferCleanerMethod = lookup.unreflect(m);
+        final Class<?> cleanerClass = directBufferCleanerMethod.type().returnType();
+
+        /* "Compile" a MH that basically is equivalent to the following code:
+         * void unmapper(ByteBuffer byteBuffer) {
+         *   sun.misc.Cleaner cleaner = ((java.nio.DirectByteBuffer) byteBuffer).cleaner();
+         *   if (Objects.nonNull(cleaner)) {
+         *     cleaner.clean();
+         *   } else {
+         *     noop(cleaner); // the noop is needed because MethodHandles#guardWithTest always needs ELSE
+         *   }
+         * }
+         */
+        final MethodHandle cleanMethod = lookup.findVirtual(cleanerClass, "clean", methodType(void.class));
+        final MethodHandle nonNullTest = lookup.findStatic(Objects.class, "nonNull", methodType(boolean.class, Object.class))
+            .asType(methodType(boolean.class, cleanerClass));
+        final MethodHandle noop = dropArguments(constant(Void.class, null).asType(methodType(void.class)), 0, cleanerClass);
+        final MethodHandle unmapper = filterReturnValue(directBufferCleanerMethod, guardWithTest(nonNullTest, cleanMethod, noop))
+            .asType(methodType(void.class, ByteBuffer.class));
+        return newBufferCleaner(directBufferClass, unmapper);
+      }
+    } catch (SecurityException se) {
+      return "Unmapping is not supported, because not all required permissions are given to the Lucene JAR file: "
+          + se + " [Please grant at least the following permissions: RuntimePermission(\"accessClassInPackage.sun.misc\") "
+          + " and ReflectPermission(\"suppressAccessChecks\")]";
+    } catch (ReflectiveOperationException | RuntimeException e) {
+      return "Unmapping is not supported on this platform, because internal Java APIs are not compatible with this Atomix version: " + e;
+    }
+  }
+
+  private static Cleaner newBufferCleaner(final Class<?> unmappableBufferClass, final MethodHandle unmapper) {
+    return (ByteBuffer buffer) -> {
+      if (!buffer.isDirect()) {
+        return;
+      }
+      if (!unmappableBufferClass.isInstance(buffer)) {
+        throw new IllegalArgumentException("buffer is not an instance of " + unmappableBufferClass.getName());
+      }
+      final Throwable error = AccessController.doPrivileged((PrivilegedAction<Throwable>) () -> {
+        try {
+          unmapper.invokeExact(buffer);
+          return null;
+        } catch (Throwable t) {
+          return t;
+        }
+      });
+      if (error != null) {
+        throw new IOException("Unable to unmap the mapped buffer", error);
+      }
+    };
+  }
+
+  /**
+   * Free {@link ByteBuffer} if possible.
+   */
+  public static void freeBuffer(ByteBuffer buffer) throws IOException {
+    CLEANER.freeBuffer(buffer);
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/Cleaner.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/Cleaner.java
new file mode 100644 (file)
index 0000000..f0027c3
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.memory;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+@FunctionalInterface
+interface Cleaner {
+
+  /**
+   * Free {@link ByteBuffer} if possible.
+   */
+  void freeBuffer(ByteBuffer buffer) throws IOException;
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MappedMemory.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MappedMemory.java
new file mode 100644 (file)
index 0000000..19a370d
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.memory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Mapped memory.
+ * <p>
+ * This is a special memory descriptor that handles management of {@link MappedByteBuffer} based memory. The
+ * mapped memory descriptor simply points to the memory address of the underlying byte buffer. When memory is reallocated,
+ * the parent {@link MappedMemoryAllocator} is used to create a new {@link MappedByteBuffer}
+ * and free the existing buffer.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class MappedMemory implements Memory {
+  private static final long MAX_SIZE = Integer.MAX_VALUE - 5;
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(MappedMemory.class);
+
+  /**
+   * Allocates memory mapped to a file on disk.
+   *
+   * @param file The file to which to map memory.
+   * @param size The count of the memory to map.
+   * @return The mapped memory.
+   * @throws IllegalArgumentException If {@code count} is greater than {@link MappedMemory#MAX_SIZE}
+   */
+  public static MappedMemory allocate(File file, int size) {
+    return new MappedMemoryAllocator(file).allocate(size);
+  }
+
+  /**
+   * Allocates memory mapped to a file on disk.
+   *
+   * @param file The file to which to map memory.
+   * @param mode The mode with which to map memory.
+   * @param size The count of the memory to map.
+   * @return The mapped memory.
+   * @throws IllegalArgumentException If {@code count} is greater than {@link MappedMemory#MAX_SIZE}
+   */
+  public static MappedMemory allocate(File file, FileChannel.MapMode mode, int size) {
+    if (size > MAX_SIZE) {
+      throw new IllegalArgumentException("size cannot be greater than " + MAX_SIZE);
+    }
+    return new MappedMemoryAllocator(file, mode).allocate(size);
+  }
+
+  private final MappedByteBuffer buffer;
+  private final MappedMemoryAllocator allocator;
+  private final int size;
+
+  public MappedMemory(MappedByteBuffer buffer, MappedMemoryAllocator allocator) {
+    this.buffer = buffer;
+    this.allocator = allocator;
+    this.size = buffer.capacity();
+  }
+
+  /**
+   * Flushes the mapped buffer to disk.
+   */
+  public void flush() {
+    buffer.force();
+  }
+
+  @Override
+  public int size() {
+    return size;
+  }
+
+  @Override
+  public void free() {
+    try {
+      BufferCleaner.freeBuffer(buffer);
+    } catch (Exception e) {
+      if (LOGGER.isDebugEnabled()) {
+        LOGGER.debug("Failed to unmap direct buffer", e);
+      }
+    }
+    allocator.release();
+  }
+
+  public void close() {
+    free();
+    allocator.close();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MappedMemoryAllocator.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MappedMemoryAllocator.java
new file mode 100644 (file)
index 0000000..7a3a270
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.memory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Mapped memory allocator.
+ * <p>
+ * The mapped memory allocator provides direct memory access to memory mapped from a file on disk. The mapped allocator
+ * supports allocating memory in any {@link FileChannel.MapMode}. Once the file is mapped and the
+ * memory has been allocated, the mapped allocator provides the memory address of the underlying
+ * {@link java.nio.MappedByteBuffer} for access via {@link sun.misc.Unsafe}.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public class MappedMemoryAllocator implements MemoryAllocator<MappedMemory> {
+  public static final FileChannel.MapMode DEFAULT_MAP_MODE = FileChannel.MapMode.READ_WRITE;
+
+  private final AtomicInteger referenceCount = new AtomicInteger();
+  private final RandomAccessFile file;
+  private final FileChannel channel;
+  private final FileChannel.MapMode mode;
+  private final long offset;
+
+  public MappedMemoryAllocator(File file) {
+    this(file, DEFAULT_MAP_MODE, 0);
+  }
+
+  public MappedMemoryAllocator(File file, FileChannel.MapMode mode) {
+    this(file, mode, 0);
+  }
+
+  public MappedMemoryAllocator(File file, FileChannel.MapMode mode, long offset) {
+    this(createFile(file, mode), mode, offset);
+  }
+
+  public MappedMemoryAllocator(RandomAccessFile file, FileChannel.MapMode mode, long offset) {
+    if (file == null) {
+      throw new NullPointerException("file cannot be null");
+    }
+    if (mode == null) {
+      throw new NullPointerException("mode cannot be null");
+    }
+    if (offset < 0) {
+      throw new IllegalArgumentException("offset cannot be negative");
+    }
+    this.file = file;
+    this.channel = this.file.getChannel();
+    this.mode = mode;
+    this.offset = offset;
+  }
+
+  private static RandomAccessFile createFile(File file, FileChannel.MapMode mode) {
+    if (file == null) {
+      throw new NullPointerException("file cannot be null");
+    }
+    if (mode == null) {
+      mode = DEFAULT_MAP_MODE;
+    }
+    try {
+      return new RandomAccessFile(file, parseMode(mode));
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static String parseMode(FileChannel.MapMode mode) {
+    if (mode == FileChannel.MapMode.READ_ONLY) {
+      return "r";
+    } else if (mode == FileChannel.MapMode.READ_WRITE) {
+      return "rw";
+    }
+    throw new IllegalArgumentException("unsupported map mode");
+  }
+
+  @Override
+  public MappedMemory allocate(int size) {
+    try {
+      if (file.length() < size) {
+        file.setLength(size);
+      }
+      referenceCount.incrementAndGet();
+      return new MappedMemory(channel.map(mode, offset, size), this);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public MappedMemory reallocate(MappedMemory memory, int size) {
+    MappedMemory newMemory = allocate(size);
+    memory.free();
+    return newMemory;
+  }
+
+  public void close() {
+    try {
+      file.close();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Releases a reference from the allocator.
+   */
+  void release() {
+    if (referenceCount.decrementAndGet() == 0) {
+      close();
+    }
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/Memory.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/Memory.java
new file mode 100644 (file)
index 0000000..5793664
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.memory;
+
+/**
+ * Memory allocator.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface Memory {
+
+  /**
+   * Returns the memory count.
+   *
+   * @return The memory count.
+   */
+  int size();
+
+  /**
+   * Frees the memory.
+   */
+  void free();
+
+  /**
+   * Memory utilities.
+   */
+  class Util {
+
+    /**
+     * Returns a boolean indicating whether the given count is a power of 2.
+     */
+    public static boolean isPow2(int size) {
+      return size > 0 & (size & (size - 1)) == 0;
+    }
+
+    /**
+     * Rounds the count to the nearest power of two.
+     */
+    public static long toPow2(int size) {
+      if ((size & (size - 1)) == 0) {
+        return size;
+      }
+      int i = 128;
+      while (i < size) {
+        i *= 2;
+        if (i <= 0) {
+          return 1L << 62;
+        }
+      }
+      return i;
+    }
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MemoryAllocator.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MemoryAllocator.java
new file mode 100644 (file)
index 0000000..87cf3cc
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.memory;
+
+/**
+ * Memory allocator.
+ * <p>
+ * Memory allocators handle allocation of memory for {@link Memory} objects.
+ *
+ * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
+ */
+public interface MemoryAllocator<T extends Memory> {
+
+  /**
+   * Allocates memory.
+   *
+   * @param size The count of the memory to allocate.
+   * @return The allocated memory.
+   */
+  T allocate(int size);
+
+  /**
+   * Reallocates the given memory.
+   * <p>
+   * When the memory is reallocated, the memory address for the given {@link Memory} instance may change. The returned
+   * {@link Memory} instance will contain the updated address and count.
+   *
+   * @param memory The memory to reallocate.
+   * @param size   The count to which to reallocate the given memory.
+   * @return The reallocated memory.
+   */
+  T reallocate(T memory, int size);
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MemorySize.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/MemorySize.java
new file mode 100644 (file)
index 0000000..0e46425
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.memory;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Memory size.
+ */
+public class MemorySize {
+
+  /**
+   * Creates a memory size from the given bytes.
+   *
+   * @param bytes the number of bytes
+   * @return the memory size
+   */
+  public static MemorySize from(long bytes) {
+    return new MemorySize(bytes);
+  }
+
+  private final long bytes;
+
+  public MemorySize(long bytes) {
+    this.bytes = bytes;
+  }
+
+  /**
+   * Returns the number of bytes.
+   *
+   * @return the number of bytes
+   */
+  public long bytes() {
+    return bytes;
+  }
+
+  @Override
+  public int hashCode() {
+    return Long.valueOf(bytes).hashCode();
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    return object instanceof MemorySize && ((MemorySize) object).bytes == bytes;
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .addValue(bytes)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/memory/package-info.java
new file mode 100644 (file)
index 0000000..6827ff8
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides classes and interfaces for performing low-level on- and off-heap memory management.
+ */
+package io.atomix.utils.memory;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/ArraySizeHashPrinter.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/ArraySizeHashPrinter.java
new file mode 100644 (file)
index 0000000..0bf5d0d
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.atomix.utils.misc;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * Helper to print Object[] length and hashCode.
+ */
+public final class ArraySizeHashPrinter {
+
+  /**
+   * Returns ByteArraySizeHashPrinter wrapping given short[].
+   *
+   * @param array arrays to wrap around
+   * @return ObjectArraySizeHashPrinter
+   */
+  public static ArraySizeHashPrinter of(byte[] array) {
+    return new ArraySizeHashPrinter(toObjectArray(array), byte[].class);
+  }
+
+  /**
+   * Returns ByteArraySizeHashPrinter wrapping given short[].
+   *
+   * @param array arrays to wrap around
+   * @return ObjectArraySizeHashPrinter
+   */
+  public static ArraySizeHashPrinter of(short[] array) {
+    return new ArraySizeHashPrinter(toObjectArray(array), short[].class);
+  }
+
+  /**
+   * Returns ByteArraySizeHashPrinter wrapping given int[].
+   *
+   * @param array arrays to wrap around
+   * @return ObjectArraySizeHashPrinter
+   */
+  public static ArraySizeHashPrinter of(int[] array) {
+    return new ArraySizeHashPrinter(toObjectArray(array), int[].class);
+  }
+
+  /**
+   * Returns ByteArraySizeHashPrinter wrapping given long[].
+   *
+   * @param array arrays to wrap around
+   * @return ObjectArraySizeHashPrinter
+   */
+  public static ArraySizeHashPrinter of(long[] array) {
+    return new ArraySizeHashPrinter(toObjectArray(array), long[].class);
+  }
+
+  /**
+   * Returns ByteArraySizeHashPrinter wrapping given float[].
+   *
+   * @param array arrays to wrap around
+   * @return ObjectArraySizeHashPrinter
+   */
+  public static ArraySizeHashPrinter of(float[] array) {
+    return new ArraySizeHashPrinter(toObjectArray(array), float[].class);
+  }
+
+  /**
+   * Returns ByteArraySizeHashPrinter wrapping given double[].
+   *
+   * @param array arrays to wrap around
+   * @return ObjectArraySizeHashPrinter
+   */
+  public static ArraySizeHashPrinter of(double[] array) {
+    return new ArraySizeHashPrinter(toObjectArray(array), double[].class);
+  }
+
+  /**
+   * Returns ByteArraySizeHashPrinter wrapping given boolean[].
+   *
+   * @param array arrays to wrap around
+   * @return ObjectArraySizeHashPrinter
+   */
+  public static ArraySizeHashPrinter of(boolean[] array) {
+    return new ArraySizeHashPrinter(toObjectArray(array), boolean[].class);
+  }
+
+  /**
+   * Returns ByteArraySizeHashPrinter wrapping given Object[].
+   *
+   * @param array arrays to wrap around
+   * @return ObjectArraySizeHashPrinter
+   */
+  public static ArraySizeHashPrinter of(Object[] array) {
+    return new ArraySizeHashPrinter(array, Object[].class);
+  }
+
+  private static Object[] toObjectArray(Object val) {
+    if (val == null) {
+      return null;
+    }
+    if (val instanceof Object[]) {
+      return (Object[]) val;
+    }
+    int length = Array.getLength(val);
+    Object[] outputArray = new Object[length];
+    for (int i = 0; i < length; ++i) {
+      outputArray[i] = Array.get(val, i);
+    }
+    return outputArray;
+  }
+
+  private final Object[] array;
+  private final Class<?> type;
+
+  public ArraySizeHashPrinter(Object[] array, Class<?> type) {
+    this.array = array;
+    this.type = type;
+  }
+
+  @Override
+  public String toString() {
+    ToStringHelper helper = MoreObjects.toStringHelper(type);
+    if (array != null) {
+      helper.add("length", array.length)
+          .add("hash", Arrays.hashCode(array));
+    } else {
+      helper.addValue(array);
+    }
+    return helper.toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/Match.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/Match.java
new file mode 100644 (file)
index 0000000..b46b0cf
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.misc;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Function;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Utility class for checking matching values.
+ *
+ * @param <T> type of value
+ */
+public final class Match<T> {
+
+  public static final Match ANY = new Match<>();
+  public static final Match NULL = new Match<>(null, false);
+  public static final Match NOT_NULL = new Match<>(null, true);
+
+  private final boolean matchAny;
+  private final T value;
+  private final boolean negation;
+
+  /**
+   * Returns a Match that matches any value including null.
+   *
+   * @param <T> match type
+   * @return new instance
+   */
+  public static <T> Match<T> any() {
+    return ANY;
+  }
+
+  /**
+   * Returns a Match that matches null values.
+   *
+   * @param <T> match type
+   * @return new instance
+   */
+  public static <T> Match<T> ifNull() {
+    return NULL;
+  }
+
+  /**
+   * Returns a Match that matches all non-null values.
+   *
+   * @param <T> match type
+   * @return new instance
+   */
+  public static <T> Match<T> ifNotNull() {
+    return NOT_NULL;
+  }
+
+  /**
+   * Returns a Match that only matches the specified value.
+   *
+   * @param value value to match
+   * @param <T>   match type
+   * @return new instance
+   */
+  public static <T> Match<T> ifValue(T value) {
+    return new Match<>(value, false);
+  }
+
+  /**
+   * Returns a Match that matches any value except the specified value.
+   *
+   * @param value value to not match
+   * @param <T>   match type
+   * @return new instance
+   */
+  public static <T> Match<T> ifNotValue(T value) {
+    return new Match<>(value, true);
+  }
+
+  private Match() {
+    matchAny = true;
+    negation = false;
+    value = null;
+  }
+
+  private Match(T value, boolean negation) {
+    matchAny = false;
+    this.value = value;
+    this.negation = negation;
+  }
+
+  /**
+   * Maps this instance to a Match of another type.
+   *
+   * @param mapper transformation function
+   * @param <V>    new match type
+   * @return new instance
+   */
+  public <V> Match<V> map(Function<T, V> mapper) {
+    if (matchAny) {
+      return any();
+    } else if (value == null) {
+      return negation ? ifNotNull() : ifNull();
+    } else {
+      return negation ? ifNotValue(mapper.apply(value)) : ifValue(mapper.apply(value));
+    }
+  }
+
+  /**
+   * Checks if this instance matches specified value.
+   *
+   * @param other other value
+   * @return true if matches; false otherwise
+   */
+  public boolean matches(T other) {
+    if (matchAny) {
+      return true;
+    } else if (other == null) {
+      return negation ? value != null : value == null;
+    } else {
+      if (value instanceof byte[]) {
+        boolean equal = Arrays.equals((byte[]) value, (byte[]) other);
+        return negation ? !equal : equal;
+      }
+      return negation ? !Objects.equals(value, other) : Objects.equals(value, other);
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(matchAny, value, negation);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (!(other instanceof Match)) {
+      return false;
+    }
+    Match<T> that = (Match<T>) other;
+    return this.matchAny == that.matchAny
+        && Objects.equals(this.value, that.value)
+        && this.negation == that.negation;
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("matchAny", matchAny)
+        .add("negation", negation)
+        .add("value", value)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/SlidingWindowCounter.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/SlidingWindowCounter.java
new file mode 100644 (file)
index 0000000..154586b
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.misc;
+
+import io.atomix.utils.concurrent.Scheduled;
+import io.atomix.utils.concurrent.SingleThreadContext;
+import io.atomix.utils.concurrent.ThreadContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Maintains a sliding window of value counts. The sliding window counter is
+ * initialized with a number of window slots. Calls to #incrementCount() will
+ * increment the value in the current window slot. Periodically the window
+ * slides and the oldest value count is dropped. Calls to #get() will get the
+ * total count for the last N window slots.
+ */
+public final class SlidingWindowCounter {
+  private final Logger log = LoggerFactory.getLogger(getClass());
+  private volatile int headSlot;
+  private final int windowSlots;
+
+  private final List<AtomicLong> counters;
+
+  private final Scheduled schedule;
+
+  private static final int SLIDE_WINDOW_PERIOD_SECONDS = 1;
+
+  public SlidingWindowCounter(int windowSlots) {
+    this(windowSlots, new SingleThreadContext("sliding-window-counter-%d"));
+  }
+
+  /**
+   * Creates a new sliding window counter with the given total number of
+   * window slots.
+   *
+   * @param windowSlots total number of window slots
+   */
+  public SlidingWindowCounter(int windowSlots, ThreadContext context) {
+    checkArgument(windowSlots > 0, "Window size must be a positive integer");
+
+    this.windowSlots = windowSlots;
+    this.headSlot = 0;
+
+    // Initialize each item in the list to an AtomicLong of 0
+    this.counters = Collections.nCopies(windowSlots, 0)
+        .stream()
+        .map(AtomicLong::new)
+        .collect(Collectors.toCollection(ArrayList::new));
+    this.schedule = context.schedule(0, SLIDE_WINDOW_PERIOD_SECONDS, TimeUnit.SECONDS, this::advanceHead);
+  }
+
+  /**
+   * Releases resources used by the SlidingWindowCounter.
+   */
+  public void destroy() {
+    schedule.cancel();
+  }
+
+  /**
+   * Increments the count of the current window slot by 1.
+   */
+  public void incrementCount() {
+    incrementCount(headSlot, 1);
+  }
+
+  /**
+   * Increments the count of the current window slot by the given value.
+   *
+   * @param value value to increment by
+   */
+  public void incrementCount(long value) {
+    incrementCount(headSlot, value);
+  }
+
+  private void incrementCount(int slot, long value) {
+    counters.get(slot).addAndGet(value);
+  }
+
+  /**
+   * Gets the total count for the last N window slots.
+   *
+   * @param slots number of slots to include in the count
+   * @return total count for last N slots
+   */
+  public long get(int slots) {
+    checkArgument(slots <= windowSlots,
+        "Requested window must be less than the total window slots");
+
+    long sum = 0;
+
+    for (int i = 0; i < slots; i++) {
+      int currentIndex = headSlot - i;
+      if (currentIndex < 0) {
+        currentIndex = counters.size() + currentIndex;
+      }
+      sum += counters.get(currentIndex).get();
+    }
+
+    return sum;
+  }
+
+  void advanceHead() {
+    counters.get(slotAfter(headSlot)).set(0);
+    headSlot = slotAfter(headSlot);
+  }
+
+  private int slotAfter(int slot) {
+    return (slot + 1) % windowSlots;
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/StringUtils.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/StringUtils.java
new file mode 100644 (file)
index 0000000..5390b6c
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.misc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Collection of various helper methods to manipulate strings.
+ */
+public final class StringUtils {
+
+  private StringUtils() {
+  }
+
+  /**
+   * Splits the input string with the given regex and filters empty strings.
+   *
+   * @param input the string to split.
+   * @return the array of strings computed by splitting this string
+   */
+  public static String[] split(String input, String regex) {
+    if (input == null) {
+      return null;
+    }
+    String[] arr = input.split(regex);
+    List<String> results = new ArrayList<>(arr.length);
+    for (String a : arr) {
+      if (!a.trim().isEmpty()) {
+        results.add(a);
+      }
+    }
+    return results.toArray(new String[0]);
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/TimestampPrinter.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/TimestampPrinter.java
new file mode 100644 (file)
index 0000000..0e4dcea
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.misc;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Timestamp printer.
+ */
+public class TimestampPrinter {
+
+  /**
+   * Returns a new timestamp printer.
+   *
+   * @param timestamp the timestamp to print
+   * @return the timestamp printer
+   */
+  public static TimestampPrinter of(long timestamp) {
+    return new TimestampPrinter(timestamp);
+  }
+
+  private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss,SSS");
+
+  private final long timestamp;
+
+  public TimestampPrinter(long timestamp) {
+    this.timestamp = timestamp;
+  }
+
+  @Override
+  public String toString() {
+    return FORMATTER.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()));
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/misc/package-info.java
new file mode 100644 (file)
index 0000000..4a3a245
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Miscellaneous utilities.
+ */
+package io.atomix.utils.misc;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/net/Address.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/net/Address.java
new file mode 100644 (file)
index 0000000..8f85edb
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.net;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/**
+ * Representation of a network address.
+ */
+public final class Address {
+  private static final int DEFAULT_PORT = 5679;
+
+  /**
+   * Address type.
+   */
+  public enum Type {
+    IPV4,
+    IPV6,
+  }
+
+  /**
+   * Returns an address that binds to all interfaces.
+   *
+   * @return the address
+   */
+  public static Address local() {
+    return from(DEFAULT_PORT);
+  }
+
+  /**
+   * Returns the address from the given host:port string.
+   *
+   * @param address the address string
+   * @return the address
+   */
+  public static Address from(String address) {
+    int lastColon = address.lastIndexOf(':');
+    int openBracket = address.indexOf('[');
+    int closeBracket = address.indexOf(']');
+
+    String host;
+    if (openBracket != -1 && closeBracket != -1) {
+      host = address.substring(openBracket + 1, closeBracket);
+    } else if (lastColon != -1) {
+      host = address.substring(0, lastColon);
+    } else {
+      host = address;
+    }
+
+    int port;
+    if (lastColon != -1) {
+      try {
+        port = Integer.parseInt(address.substring(lastColon + 1));
+      } catch (NumberFormatException e) {
+        throw new MalformedAddressException(address, e);
+      }
+    } else {
+      port = DEFAULT_PORT;
+    }
+    return new Address(host, port);
+  }
+
+  /**
+   * Returns an address for the given host/port.
+   *
+   * @param host the host name
+   * @param port the port
+   * @return a new address
+   */
+  public static Address from(String host, int port) {
+    return new Address(host, port);
+  }
+
+  /**
+   * Returns an address for the local host and the given port.
+   *
+   * @param port the port
+   * @return a new address
+   */
+  public static Address from(int port) {
+    try {
+      InetAddress address = getLocalAddress();
+      return new Address(address.getHostName(), port);
+    } catch (UnknownHostException e) {
+      throw new IllegalArgumentException("Failed to locate host", e);
+    }
+  }
+
+  /**
+   * Returns the local host.
+   */
+  private static InetAddress getLocalAddress() throws UnknownHostException {
+    try {
+      return InetAddress.getLocalHost();  // first NIC
+    } catch (Exception ignore) {
+      return InetAddress.getByName(null);
+    }
+  }
+
+  private final String host;
+  private final int port;
+  private transient volatile Type type;
+  private transient volatile InetAddress address;
+
+  public Address(String host, int port) {
+    this(host, port, null);
+  }
+
+  public Address(String host, int port, InetAddress address) {
+    this.host = host;
+    this.port = port;
+    this.address = address;
+    if (address != null) {
+      this.type = address instanceof Inet6Address ? Type.IPV6 : Type.IPV4;
+    }
+  }
+
+  /**
+   * Returns the host name.
+   *
+   * @return the host name
+   */
+  public String host() {
+    return host;
+  }
+
+  /**
+   * Returns the port.
+   *
+   * @return the port
+   */
+  public int port() {
+    return port;
+  }
+
+  /**
+   * Returns the IP address.
+   *
+   * @return the IP address
+   */
+  public InetAddress address() {
+    return address(false);
+  }
+
+  /**
+   * Returns the IP address.
+   *
+   * @param resolve whether to force resolve the hostname
+   * @return the IP address
+   */
+  public InetAddress address(boolean resolve) {
+    if (resolve) {
+      address = resolveAddress();
+      return address;
+    }
+
+    if (address == null) {
+      synchronized (this) {
+        if (address == null) {
+          address = resolveAddress();
+        }
+      }
+    }
+    return address;
+  }
+
+  /**
+   * Resolves the IP address from the hostname.
+   *
+   * @return the resolved IP address or {@code null} if the IP could not be resolved
+   */
+  private InetAddress resolveAddress() {
+    try {
+      return InetAddress.getByName(host);
+    } catch (UnknownHostException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Returns the address type.
+   *
+   * @return the address type
+   */
+  public Type type() {
+    if (type == null) {
+      synchronized (this) {
+        if (type == null) {
+          type = address() instanceof Inet6Address ? Type.IPV6 : Type.IPV4;
+        }
+      }
+    }
+    return type;
+  }
+
+  @Override
+  public String toString() {
+    String host = host();
+    int port = port();
+    if (host.matches("([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}")) {
+      return String.format("[%s]:%d", host, port);
+    } else {
+      return String.format("%s:%d", host, port);
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(host, port);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof Address)) {
+      return false;
+    }
+    Address that = (Address) obj;
+    return this.host.equals(that.host)
+        && this.port == that.port;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/net/MalformedAddressException.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/net/MalformedAddressException.java
new file mode 100644 (file)
index 0000000..11a5364
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.net;
+
+import io.atomix.utils.AtomixRuntimeException;
+
+/**
+ * Malformed address exception.
+ */
+public class MalformedAddressException extends AtomixRuntimeException {
+  public MalformedAddressException(String message) {
+    super(message);
+  }
+
+  public MalformedAddressException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/net/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/net/package-info.java
new file mode 100644 (file)
index 0000000..82dc154
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides classes and interfaces for representing and operating on IP addresses.
+ */
+package io.atomix.utils.net;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/package-info.java
new file mode 100644 (file)
index 0000000..6c1341b
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides utility classes and interfaces used throughout Atomix projects.
+ */
+package io.atomix.utils;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/BufferAwareByteArrayOutputStream.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/BufferAwareByteArrayOutputStream.java
new file mode 100644 (file)
index 0000000..19cd0f2
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Exposes protected byte array length in {@link ByteArrayOutputStream}.
+ */
+final class BufferAwareByteArrayOutputStream extends ByteArrayOutputStream {
+
+  BufferAwareByteArrayOutputStream(int size) {
+    super(size);
+  }
+
+  int getBufferSize() {
+    return buf.length;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/ByteArrayOutput.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/ByteArrayOutput.java
new file mode 100644 (file)
index 0000000..0289123
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Convenience class to avoid extra object allocation and casting.
+ */
+final class ByteArrayOutput extends Output {
+
+  private final BufferAwareByteArrayOutputStream stream;
+
+  ByteArrayOutput(final int bufferSize, final int maxBufferSize, final BufferAwareByteArrayOutputStream stream) {
+    super(bufferSize, maxBufferSize);
+    super.setOutputStream(stream);
+    this.stream = stream;
+  }
+
+  BufferAwareByteArrayOutputStream getByteArrayOutputStream() {
+    return stream;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/KryoIOPool.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/KryoIOPool.java
new file mode 100644 (file)
index 0000000..0f40cfc
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import java.lang.ref.SoftReference;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.Function;
+
+abstract class KryoIOPool<T> {
+
+  private final Queue<SoftReference<T>> queue = new ConcurrentLinkedQueue<>();
+
+  private T borrow(final int bufferSize) {
+    T element;
+    SoftReference<T> reference;
+    while ((reference = queue.poll()) != null) {
+      if ((element = reference.get()) != null) {
+        return element;
+      }
+    }
+    return create(bufferSize);
+  }
+
+  protected abstract T create(final int bufferSize);
+
+  protected abstract boolean recycle(final T element);
+
+  <R> R run(final Function<T, R> function, final int bufferSize) {
+    final T element = borrow(bufferSize);
+    try {
+      return function.apply(element);
+    } finally {
+      if (recycle(element)) {
+        queue.offer(new SoftReference<>(element));
+      }
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/KryoInputPool.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/KryoInputPool.java
new file mode 100644 (file)
index 0000000..8eb4754
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import com.esotericsoftware.kryo.io.Input;
+
+class KryoInputPool extends KryoIOPool<Input> {
+
+  static final int MAX_POOLED_BUFFER_SIZE = 512 * 1024;
+
+  @Override
+  protected Input create(int bufferSize) {
+    return new Input(bufferSize);
+  }
+
+  @Override
+  protected boolean recycle(Input input) {
+    if (input.getBuffer().length < MAX_POOLED_BUFFER_SIZE) {
+      input.setInputStream(null);
+      return true;
+    }
+    return false; // discard
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/KryoOutputPool.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/KryoOutputPool.java
new file mode 100644 (file)
index 0000000..1c245a2
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+class KryoOutputPool extends KryoIOPool<ByteArrayOutput> {
+
+  private static final int MAX_BUFFER_SIZE = 768 * 1024;
+  static final int MAX_POOLED_BUFFER_SIZE = 512 * 1024;
+
+  @Override
+  protected ByteArrayOutput create(int bufferSize) {
+    return new ByteArrayOutput(bufferSize, MAX_BUFFER_SIZE, new BufferAwareByteArrayOutputStream(bufferSize));
+  }
+
+  @Override
+  protected boolean recycle(ByteArrayOutput output) {
+    if (output.getByteArrayOutputStream().getBufferSize() < MAX_POOLED_BUFFER_SIZE) {
+      output.getByteArrayOutputStream().reset();
+      output.clear();
+      return true;
+    }
+    return false; // discard
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/Namespace.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/Namespace.java
new file mode 100644 (file)
index 0000000..46e016c
--- /dev/null
@@ -0,0 +1,639 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Registration;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.ByteBufferInput;
+import com.esotericsoftware.kryo.io.ByteBufferOutput;
+import com.esotericsoftware.kryo.pool.KryoCallback;
+import com.esotericsoftware.kryo.pool.KryoFactory;
+import com.esotericsoftware.kryo.pool.KryoPool;
+import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import io.atomix.utils.config.ConfigurationException;
+import org.apache.commons.lang3.tuple.Pair;
+import org.objenesis.strategy.StdInstantiatorStrategy;
+import org.slf4j.Logger;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Pool of Kryo instances, with classes pre-registered.
+ */
+//@ThreadSafe
+public final class Namespace implements KryoFactory, KryoPool {
+
+  /**
+   * Default buffer size used for serialization.
+   *
+   * @see #serialize(Object)
+   */
+  public static final int DEFAULT_BUFFER_SIZE = 4096;
+
+  /**
+   * Maximum allowed buffer size.
+   */
+  public static final int MAX_BUFFER_SIZE = 100 * 1000 * 1000;
+
+  /**
+   * ID to use if this KryoNamespace does not define registration id.
+   */
+  public static final int FLOATING_ID = -1;
+
+  /**
+   * Smallest ID free to use for user defined registrations.
+   */
+  public static final int INITIAL_ID = 16;
+
+  static final String NO_NAME = "(no name)";
+
+  private static final Logger LOGGER = getLogger(Namespace.class);
+
+  /**
+   * Default Kryo namespace.
+   */
+  public static final Namespace DEFAULT = builder().build();
+
+  private final KryoPool kryoPool = new KryoPool.Builder(this)
+      .softReferences()
+      .build();
+
+  private final KryoOutputPool kryoOutputPool = new KryoOutputPool();
+  private final KryoInputPool kryoInputPool = new KryoInputPool();
+
+  private final ImmutableList<RegistrationBlock> registeredBlocks;
+
+  private final ClassLoader classLoader;
+  private final boolean compatible;
+  private final boolean registrationRequired;
+  private final String friendlyName;
+
+  /**
+   * KryoNamespace builder.
+   */
+  //@NotThreadSafe
+  public static final class Builder {
+    private int blockHeadId = INITIAL_ID;
+    private List<Pair<Class<?>[], Serializer<?>>> types = new ArrayList<>();
+    private List<RegistrationBlock> blocks = new ArrayList<>();
+    private ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+    private boolean registrationRequired = true;
+    private boolean compatible = false;
+
+    /**
+     * Builds a {@link Namespace} instance.
+     *
+     * @return KryoNamespace
+     */
+    public Namespace build() {
+      return build(NO_NAME);
+    }
+
+    /**
+     * Builds a {@link Namespace} instance.
+     *
+     * @param friendlyName friendly name for the namespace
+     * @return KryoNamespace
+     */
+    public Namespace build(String friendlyName) {
+      if (!types.isEmpty()) {
+        blocks.add(new RegistrationBlock(this.blockHeadId, types));
+      }
+      return new Namespace(blocks, classLoader, registrationRequired, compatible, friendlyName).populate(1);
+    }
+
+    /**
+     * Sets the next Kryo registration Id for following register entries.
+     *
+     * @param id Kryo registration Id
+     * @return this
+     * @see Kryo#register(Class, Serializer, int)
+     */
+    public Builder nextId(final int id) {
+      if (!types.isEmpty()) {
+        if (id != FLOATING_ID && id < blockHeadId + types.size()) {
+
+          if (LOGGER.isWarnEnabled()) {
+            LOGGER.warn("requested nextId {} could potentially overlap "
+                    + "with existing registrations {}+{} ",
+                id, blockHeadId, types.size(), new RuntimeException());
+          }
+        }
+        blocks.add(new RegistrationBlock(this.blockHeadId, types));
+        types = new ArrayList<>();
+      }
+      this.blockHeadId = id;
+      return this;
+    }
+
+    /**
+     * Registers classes to be serialized using Kryo default serializer.
+     *
+     * @param expectedTypes list of classes
+     * @return this
+     */
+    public Builder register(final Class<?>... expectedTypes) {
+      for (Class<?> clazz : expectedTypes) {
+        types.add(Pair.of(new Class<?>[]{clazz}, null));
+      }
+      return this;
+    }
+
+    /**
+     * Registers serializer for the given set of classes.
+     * <p>
+     * When multiple classes are registered with an explicitly provided serializer, the namespace guarantees
+     * all instances will be serialized with the same type ID.
+     *
+     * @param classes    list of classes to register
+     * @param serializer serializer to use for the class
+     * @return this
+     */
+    public Builder register(Serializer<?> serializer, final Class<?>... classes) {
+      types.add(Pair.of(classes, checkNotNull(serializer)));
+      return this;
+    }
+
+    private Builder register(RegistrationBlock block) {
+      if (block.begin() != FLOATING_ID) {
+        // flush pending types
+        nextId(block.begin());
+        blocks.add(block);
+        nextId(block.begin() + block.types().size());
+      } else {
+        // flush pending types
+        final int addedBlockBegin = blockHeadId + types.size();
+        nextId(addedBlockBegin);
+        blocks.add(new RegistrationBlock(addedBlockBegin, block.types()));
+        nextId(addedBlockBegin + block.types().size());
+      }
+      return this;
+    }
+
+    /**
+     * Registers all the class registered to given KryoNamespace.
+     *
+     * @param ns KryoNamespace
+     * @return this
+     */
+    public Builder register(final Namespace ns) {
+
+      if (blocks.containsAll(ns.registeredBlocks)) {
+        // Everything was already registered.
+        LOGGER.debug("Ignoring {}, already registered.", ns);
+        return this;
+      }
+      for (RegistrationBlock block : ns.registeredBlocks) {
+        this.register(block);
+      }
+      return this;
+    }
+
+    /**
+     * Sets the namespace class loader.
+     *
+     * @param classLoader the namespace class loader
+     * @return the namespace builder
+     */
+    public Builder setClassLoader(ClassLoader classLoader) {
+      this.classLoader = classLoader;
+      return this;
+    }
+
+    /**
+     * Sets whether backwards/forwards compatible versioned serialization is enabled.
+     * <p>
+     * When compatible serialization is enabled, the {@link CompatibleFieldSerializer} will be set as the
+     * default serializer for types that do not otherwise explicitly specify a serializer.
+     *
+     * @param compatible whether versioned serialization is enabled
+     * @return this
+     */
+    public Builder setCompatible(boolean compatible) {
+      this.compatible = compatible;
+      return this;
+    }
+
+    /**
+     * Sets the registrationRequired flag.
+     *
+     * @param registrationRequired Kryo's registrationRequired flag
+     * @return this
+     * @see Kryo#setRegistrationRequired(boolean)
+     */
+    public Builder setRegistrationRequired(boolean registrationRequired) {
+      this.registrationRequired = registrationRequired;
+      return this;
+    }
+  }
+
+  /**
+   * Creates a new {@link Namespace} builder.
+   *
+   * @return builder
+   */
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @SuppressWarnings("unchecked")
+  private static List<RegistrationBlock> buildRegistrationBlocks(NamespaceConfig config) {
+    List<Pair<Class<?>[], Serializer<?>>> types = new ArrayList<>();
+    List<RegistrationBlock> blocks = new ArrayList<>();
+    blocks.addAll(Namespaces.BASIC.registeredBlocks);
+    for (NamespaceTypeConfig type : config.getTypes()) {
+      try {
+        if (type.getId() == null) {
+          types.add(Pair.of(new Class[]{type.getType()}, type.getSerializer() != null ? type.getSerializer().newInstance() : null));
+        } else {
+          blocks.add(new RegistrationBlock(type.getId(), Collections.singletonList(Pair.of(new Class[]{type.getType()}, type.getSerializer().newInstance()))));
+        }
+      } catch (InstantiationException | IllegalAccessException e) {
+        throw new ConfigurationException("Failed to instantiate serializer from configuration", e);
+      }
+    }
+    blocks.add(new RegistrationBlock(FLOATING_ID, types));
+    return blocks;
+  }
+
+  public Namespace(NamespaceConfig config) {
+    this(buildRegistrationBlocks(config), Thread.currentThread().getContextClassLoader(), config.isRegistrationRequired(), config.isCompatible(), config.getName());
+  }
+
+  /**
+   * Creates a Kryo instance pool.
+   *
+   * @param registeredTypes      types to register
+   * @param registrationRequired whether registration is required
+   * @param compatible           whether compatible serialization is enabled
+   * @param friendlyName         friendly name for the namespace
+   */
+  private Namespace(
+      final List<RegistrationBlock> registeredTypes,
+      ClassLoader classLoader,
+      boolean registrationRequired,
+      boolean compatible,
+      String friendlyName) {
+    this.registeredBlocks = ImmutableList.copyOf(registeredTypes);
+    this.registrationRequired = registrationRequired;
+    this.classLoader = classLoader;
+    this.compatible = compatible;
+    this.friendlyName = checkNotNull(friendlyName);
+  }
+
+  /**
+   * Populates the Kryo pool.
+   *
+   * @param instances to add to the pool
+   * @return this
+   */
+  public Namespace populate(int instances) {
+
+    for (int i = 0; i < instances; ++i) {
+      release(create());
+    }
+    return this;
+  }
+
+  /**
+   * Serializes given object to byte array using Kryo instance in pool.
+   * <p>
+   * Note: Serialized bytes must be smaller than {@link #MAX_BUFFER_SIZE}.
+   *
+   * @param obj Object to serialize
+   * @return serialized bytes
+   */
+  public byte[] serialize(final Object obj) {
+    return serialize(obj, DEFAULT_BUFFER_SIZE);
+  }
+
+  /**
+   * Serializes given object to byte array using Kryo instance in pool.
+   *
+   * @param obj        Object to serialize
+   * @param bufferSize maximum size of serialized bytes
+   * @return serialized bytes
+   */
+  public byte[] serialize(final Object obj, final int bufferSize) {
+    return kryoOutputPool.run(output -> {
+      return kryoPool.run(kryo -> {
+        kryo.writeClassAndObject(output, obj);
+        output.flush();
+        return output.getByteArrayOutputStream().toByteArray();
+      });
+    }, bufferSize);
+  }
+
+  /**
+   * Serializes given object to byte buffer using Kryo instance in pool.
+   *
+   * @param obj    Object to serialize
+   * @param buffer to write to
+   */
+  public void serialize(final Object obj, final ByteBuffer buffer) {
+    ByteBufferOutput out = new ByteBufferOutput(buffer);
+    Kryo kryo = borrow();
+    try {
+      kryo.writeClassAndObject(out, obj);
+      out.flush();
+    } finally {
+      release(kryo);
+    }
+  }
+
+  /**
+   * Serializes given object to OutputStream using Kryo instance in pool.
+   *
+   * @param obj    Object to serialize
+   * @param stream to write to
+   */
+  public void serialize(final Object obj, final OutputStream stream) {
+    serialize(obj, stream, DEFAULT_BUFFER_SIZE);
+  }
+
+  /**
+   * Serializes given object to OutputStream using Kryo instance in pool.
+   *
+   * @param obj        Object to serialize
+   * @param stream     to write to
+   * @param bufferSize size of the buffer in front of the stream
+   */
+  public void serialize(final Object obj, final OutputStream stream, final int bufferSize) {
+    ByteBufferOutput out = new ByteBufferOutput(stream, bufferSize);
+    Kryo kryo = borrow();
+    try {
+      kryo.writeClassAndObject(out, obj);
+      out.flush();
+    } finally {
+      release(kryo);
+    }
+  }
+
+  /**
+   * Deserializes given byte array to Object using Kryo instance in pool.
+   *
+   * @param bytes serialized bytes
+   * @param <T>   deserialized Object type
+   * @return deserialized Object
+   */
+  public <T> T deserialize(final byte[] bytes) {
+    return kryoInputPool.run(input -> {
+      input.setInputStream(new ByteArrayInputStream(bytes));
+      return kryoPool.run(kryo -> {
+        @SuppressWarnings("unchecked")
+        T obj = (T) kryo.readClassAndObject(input);
+        return obj;
+      });
+    }, DEFAULT_BUFFER_SIZE);
+  }
+
+  /**
+   * Deserializes given byte buffer to Object using Kryo instance in pool.
+   *
+   * @param buffer input with serialized bytes
+   * @param <T>    deserialized Object type
+   * @return deserialized Object
+   */
+  public <T> T deserialize(final ByteBuffer buffer) {
+    ByteBufferInput in = new ByteBufferInput(buffer);
+    Kryo kryo = borrow();
+    try {
+      @SuppressWarnings("unchecked")
+      T obj = (T) kryo.readClassAndObject(in);
+      return obj;
+    } finally {
+      release(kryo);
+    }
+  }
+
+  /**
+   * Deserializes given InputStream to an Object using Kryo instance in pool.
+   *
+   * @param stream input stream
+   * @param <T>    deserialized Object type
+   * @return deserialized Object
+   */
+  public <T> T deserialize(final InputStream stream) {
+    return deserialize(stream, DEFAULT_BUFFER_SIZE);
+  }
+
+  /**
+   * Deserializes given InputStream to an Object using Kryo instance in pool.
+   *
+   * @param stream     input stream
+   * @param <T>        deserialized Object type
+   * @param bufferSize size of the buffer in front of the stream
+   * @return deserialized Object
+   */
+  public <T> T deserialize(final InputStream stream, final int bufferSize) {
+    ByteBufferInput in = new ByteBufferInput(stream, bufferSize);
+    Kryo kryo = borrow();
+    try {
+      @SuppressWarnings("unchecked")
+      T obj = (T) kryo.readClassAndObject(in);
+      return obj;
+    } finally {
+      release(kryo);
+    }
+  }
+
+  private String friendlyName() {
+    return friendlyName;
+  }
+
+  /**
+   * Gets the number of classes registered in this Kryo namespace.
+   *
+   * @return size of namespace
+   */
+  public int size() {
+    return (int) registeredBlocks.stream()
+        .flatMap(block -> block.types().stream())
+        .count();
+  }
+
+  /**
+   * Creates a Kryo instance.
+   *
+   * @return Kryo instance
+   */
+  @Override
+  public Kryo create() {
+    LOGGER.trace("Creating Kryo instance for {}", this);
+    Kryo kryo = new Kryo();
+    kryo.setClassLoader(classLoader);
+    kryo.setRegistrationRequired(registrationRequired);
+
+    // If compatible serialization is enabled, override the default serializer.
+    if (compatible) {
+      kryo.setDefaultSerializer(CompatibleFieldSerializer::new);
+    }
+
+    // TODO rethink whether we want to use StdInstantiatorStrategy
+    kryo.setInstantiatorStrategy(
+        new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
+
+    for (RegistrationBlock block : registeredBlocks) {
+      int id = block.begin();
+      if (id == FLOATING_ID) {
+        id = kryo.getNextRegistrationId();
+      }
+      for (Pair<Class<?>[], Serializer<?>> entry : block.types()) {
+        register(kryo, entry.getLeft(), entry.getRight(), id++);
+      }
+    }
+    return kryo;
+  }
+
+  /**
+   * Register {@code type} and {@code serializer} to {@code kryo} instance.
+   *
+   * @param kryo       Kryo instance
+   * @param types      types to register
+   * @param serializer Specific serializer to register or null to use default.
+   * @param id         type registration id to use
+   */
+  private void register(Kryo kryo, Class<?>[] types, Serializer<?> serializer, int id) {
+    Registration existing = kryo.getRegistration(id);
+    if (existing != null) {
+      boolean matches = false;
+      for (Class<?> type : types) {
+        if (existing.getType() == type) {
+          matches = true;
+          break;
+        }
+      }
+
+      if (!matches) {
+        LOGGER.error("{}: Failed to register {} as {}, {} was already registered.",
+            friendlyName(), types, id, existing.getType());
+
+        throw new IllegalStateException(String.format(
+            "Failed to register %s as %s, %s was already registered.",
+            Arrays.toString(types), id, existing.getType()));
+      }
+      // falling through to register call for now.
+      // Consider skipping, if there's reasonable
+      // way to compare serializer equivalence.
+    }
+
+    for (Class<?> type : types) {
+      Registration r = null;
+      if (serializer == null) {
+        r = kryo.register(type, id);
+      } else if (type.isInterface()) {
+        kryo.addDefaultSerializer(type, serializer);
+      } else {
+        r = kryo.register(type, serializer, id);
+      }
+      if (r != null) {
+        if (r.getId() != id) {
+          LOGGER.debug("{}: {} already registered as {}. Skipping {}.",
+              friendlyName(), r.getType(), r.getId(), id);
+        }
+        LOGGER.trace("{} registered as {}", r.getType(), r.getId());
+      }
+    }
+  }
+
+  @Override
+  public Kryo borrow() {
+    return kryoPool.borrow();
+  }
+
+  @Override
+  public void release(Kryo kryo) {
+    kryoPool.release(kryo);
+  }
+
+  @Override
+  public <T> T run(KryoCallback<T> callback) {
+    return kryoPool.run(callback);
+  }
+
+  @Override
+  public String toString() {
+    if (!NO_NAME.equals(friendlyName)) {
+      return MoreObjects.toStringHelper(getClass())
+          .omitNullValues()
+          .add("friendlyName", friendlyName)
+          // omit lengthy detail, when there's a name
+          .toString();
+    }
+    return MoreObjects.toStringHelper(getClass())
+        .add("registeredBlocks", registeredBlocks)
+        .toString();
+  }
+
+  static final class RegistrationBlock {
+    private final int begin;
+    private final ImmutableList<Pair<Class<?>[], Serializer<?>>> types;
+
+    RegistrationBlock(int begin, List<Pair<Class<?>[], Serializer<?>>> types) {
+      this.begin = begin;
+      this.types = ImmutableList.copyOf(types);
+    }
+
+    public int begin() {
+      return begin;
+    }
+
+    public ImmutableList<Pair<Class<?>[], Serializer<?>>> types() {
+      return types;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(getClass())
+          .add("begin", begin)
+          .add("types", types)
+          .toString();
+    }
+
+    @Override
+    public int hashCode() {
+      return types.hashCode();
+    }
+
+    // Only the registered types are used for equality.
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+
+      if (obj instanceof RegistrationBlock) {
+        RegistrationBlock that = (RegistrationBlock) obj;
+        return Objects.equals(this.types, that.types);
+      }
+      return false;
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/NamespaceConfig.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/NamespaceConfig.java
new file mode 100644 (file)
index 0000000..bd47f71
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import io.atomix.utils.config.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Namespace configuration.
+ */
+public class NamespaceConfig implements Config {
+  private String name = Namespace.NO_NAME;
+  private boolean registrationRequired = true;
+  private boolean compatible = false;
+  private List<NamespaceTypeConfig> types = new ArrayList<>();
+
+  /**
+   * Returns the serializer name.
+   *
+   * @return the serializer name
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Sets the serializer name.
+   *
+   * @param name the serializer name
+   * @return the serializer configuration
+   */
+  public NamespaceConfig setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  /**
+   * Returns whether registration is required.
+   *
+   * @return whether registration is required
+   */
+  public boolean isRegistrationRequired() {
+    return registrationRequired;
+  }
+
+  /**
+   * Sets whether registration is required.
+   *
+   * @param registrationRequired whether registration is required
+   * @return the serializer configuration
+   */
+  public NamespaceConfig setRegistrationRequired(boolean registrationRequired) {
+    this.registrationRequired = registrationRequired;
+    return this;
+  }
+
+  /**
+   * Returns whether compatible serialization is enabled.
+   *
+   * @return whether compatible serialization is enabled
+   */
+  public boolean isCompatible() {
+    return compatible;
+  }
+
+  /**
+   * Sets whether compatible serialization is enabled.
+   *
+   * @param compatible whether compatible serialization is enabled
+   * @return the serializer configuration
+   */
+  public NamespaceConfig setCompatible(boolean compatible) {
+    this.compatible = compatible;
+    return this;
+  }
+
+  /**
+   * Returns the serializable types.
+   *
+   * @return the serializable types
+   */
+  public List<NamespaceTypeConfig> getTypes() {
+    return types;
+  }
+
+  /**
+   * Sets the serializable types.
+   *
+   * @param types the serializable types
+   * @return the serializer configuration
+   */
+  public NamespaceConfig setTypes(List<NamespaceTypeConfig> types) {
+    this.types = types;
+    return this;
+  }
+
+  /**
+   * Adds a serializable type to the configuration.
+   *
+   * @param type the serializable type to add
+   * @return the serializer configuration
+   */
+  public NamespaceConfig addType(NamespaceTypeConfig type) {
+    types.add(type);
+    return this;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/NamespaceTypeConfig.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/NamespaceTypeConfig.java
new file mode 100644 (file)
index 0000000..7305804
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import com.esotericsoftware.kryo.Serializer;
+import io.atomix.utils.config.Config;
+
+/**
+ * Namespace type configuration.
+ */
+public class NamespaceTypeConfig implements Config {
+  private Class<?> type;
+  private Integer id;
+  private Class<? extends com.esotericsoftware.kryo.Serializer> serializer;
+
+  /**
+   * Returns the serializable type.
+   *
+   * @return the serializable type
+   */
+  public Class<?> getType() {
+    return type;
+  }
+
+  /**
+   * Sets the serializable type.
+   *
+   * @param type the serializable type
+   * @return the type configuration
+   */
+  public NamespaceTypeConfig setType(Class<?> type) {
+    this.type = type;
+    return this;
+  }
+
+  /**
+   * Returns the type identifier.
+   *
+   * @return the type identifier
+   */
+  public Integer getId() {
+    return id;
+  }
+
+  /**
+   * Sets the type identifier.
+   *
+   * @param id the type identifier
+   * @return the type configuration
+   */
+  public NamespaceTypeConfig setId(Integer id) {
+    this.id = id;
+    return this;
+  }
+
+  /**
+   * Returns the serializer class.
+   *
+   * @return the serializer class
+   */
+  public Class<? extends Serializer> getSerializer() {
+    return serializer;
+  }
+
+  /**
+   * Sets the serializer class.
+   *
+   * @param serializer the serializer class
+   * @return the type configuration
+   */
+  public NamespaceTypeConfig setSerializer(Class<? extends Serializer> serializer) {
+    this.serializer = serializer;
+    return this;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/Namespaces.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/Namespaces.java
new file mode 100644 (file)
index 0000000..a6b76ab
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multisets;
+import com.google.common.collect.Sets;
+import io.atomix.utils.Version;
+import io.atomix.utils.serializer.serializers.ArraysAsListSerializer;
+import io.atomix.utils.serializer.serializers.ImmutableListSerializer;
+import io.atomix.utils.serializer.serializers.ImmutableMapSerializer;
+import io.atomix.utils.serializer.serializers.ImmutableSetSerializer;
+import io.atomix.utils.time.LogicalTimestamp;
+import io.atomix.utils.time.WallClockTimestamp;
+
+import java.time.Duration;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public final class Namespaces {
+  public static final int BASIC_MAX_SIZE = 50;
+  public static final Namespace BASIC = Namespace.builder()
+      .nextId(Namespace.FLOATING_ID)
+      .register(byte[].class)
+      .register(AtomicBoolean.class)
+      .register(AtomicInteger.class)
+      .register(AtomicLong.class)
+      .register(new ImmutableListSerializer(),
+          ImmutableList.class,
+          ImmutableList.of(1).getClass(),
+          ImmutableList.of(1, 2).getClass(),
+          ImmutableList.of(1, 2, 3).subList(1, 3).getClass())
+      .register(new ImmutableSetSerializer(),
+          ImmutableSet.class,
+          ImmutableSet.of().getClass(),
+          ImmutableSet.of(1).getClass(),
+          ImmutableSet.of(1, 2).getClass())
+      .register(new ImmutableMapSerializer(),
+          ImmutableMap.class,
+          ImmutableMap.of().getClass(),
+          ImmutableMap.of("a", 1).getClass(),
+          ImmutableMap.of("R", 2, "D", 2).getClass())
+      .register(Collections.unmodifiableSet(Collections.emptySet()).getClass())
+      .register(HashMap.class)
+      .register(ConcurrentHashMap.class)
+      .register(CopyOnWriteArraySet.class)
+      .register(
+          ArrayList.class,
+          LinkedList.class,
+          HashSet.class,
+          LinkedHashSet.class,
+          ArrayDeque.class
+      )
+      .register(HashMultiset.class)
+      .register(Multisets.immutableEntry("", 0).getClass())
+      .register(Sets.class)
+      .register(Maps.immutableEntry("a", "b").getClass())
+      .register(new ArraysAsListSerializer(), Arrays.asList().getClass())
+      .register(Collections.singletonList(1).getClass())
+      .register(Duration.class)
+      .register(Collections.emptySet().getClass())
+      .register(Optional.class)
+      .register(Collections.emptyList().getClass())
+      .register(Collections.singleton(Object.class).getClass())
+      .register(Properties.class)
+      .register(int[].class)
+      .register(long[].class)
+      .register(short[].class)
+      .register(double[].class)
+      .register(float[].class)
+      .register(char[].class)
+      .register(String[].class)
+      .register(boolean[].class)
+      .register(Object[].class)
+      .register(LogicalTimestamp.class)
+      .register(WallClockTimestamp.class)
+      .register(Version.class)
+      .build("BASIC");
+
+  /**
+   * Kryo registration Id for user custom registration.
+   */
+  public static final int BEGIN_USER_CUSTOM_ID = 500;
+
+  // not to be instantiated
+  private Namespaces() {
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/Serializer.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/Serializer.java
new file mode 100644 (file)
index 0000000..ec3aa6f
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.atomix.utils.serializer;
+
+/**
+ * Interface for serialization of store artifacts.
+ */
+public interface Serializer {
+
+  /**
+   * Creates a new serializer builder.
+   *
+   * @return a new serializer builder
+   */
+  static SerializerBuilder builder() {
+    return new SerializerBuilder();
+  }
+
+  /**
+   * Creates a new serializer builder.
+   *
+   * @param name the serializer name
+   * @return a new serializer builder
+   */
+  static SerializerBuilder builder(String name) {
+    return new SerializerBuilder(name);
+  }
+
+  /**
+   * Serialize the specified object.
+   *
+   * @param object object to serialize.
+   * @param <T>    encoded type
+   * @return serialized bytes.
+   */
+  <T> byte[] encode(T object);
+
+  /**
+   * Deserialize the specified bytes.
+   *
+   * @param bytes byte array to deserialize.
+   * @param <T>   decoded type
+   * @return deserialized object.
+   */
+  <T> T decode(byte[] bytes);
+
+  /**
+   * Creates a new Serializer instance from a Namespace.
+   *
+   * @param namespace serializer namespace
+   * @return Serializer instance
+   */
+  static Serializer using(Namespace namespace) {
+    return new Serializer() {
+      @Override
+      public <T> byte[] encode(T object) {
+        return namespace.serialize(object);
+      }
+
+      @Override
+      public <T> T decode(byte[] bytes) {
+        return namespace.deserialize(bytes);
+      }
+    };
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/SerializerBuilder.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/SerializerBuilder.java
new file mode 100644 (file)
index 0000000..0b6c82b
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import io.atomix.utils.Builder;
+
+/**
+ * Serializer builder.
+ */
+public class SerializerBuilder implements Builder<Serializer> {
+  private final String name;
+  private final Namespace.Builder namespaceBuilder = Namespace.builder()
+      .register(Namespaces.BASIC)
+      .nextId(Namespaces.BEGIN_USER_CUSTOM_ID);
+
+  public SerializerBuilder() {
+    this(null);
+  }
+
+  public SerializerBuilder(String name) {
+    this.name = name;
+  }
+
+  /**
+   * Requires explicit serializable type registration for serializable types.
+   *
+   * @return the serializer builder
+   */
+  public SerializerBuilder withRegistrationRequired() {
+    return withRegistrationRequired(true);
+  }
+
+  /**
+   * Sets whether serializable type registration is required for serializable types.
+   *
+   * @param registrationRequired whether serializable type registration is required for serializable types
+   * @return the serializer builder
+   */
+  public SerializerBuilder withRegistrationRequired(boolean registrationRequired) {
+    namespaceBuilder.setRegistrationRequired(registrationRequired);
+    return this;
+  }
+
+  /**
+   * Enables compatible serialization for serializable types.
+   *
+   * @return the serializer builder
+   */
+  public SerializerBuilder withCompatibleSerialization() {
+    return withCompatibleSerialization(true);
+  }
+
+  /**
+   * Sets whether compatible serialization is enabled for serializable types.
+   *
+   * @param compatibleSerialization whether compatible serialization is enabled for user types
+   * @return the serializer builder
+   */
+  public SerializerBuilder withCompatibleSerialization(boolean compatibleSerialization) {
+    namespaceBuilder.setCompatible(compatibleSerialization);
+    return this;
+  }
+
+  /**
+   * Adds a namespace to the serializer.
+   *
+   * @param namespace the namespace to add
+   * @return the serializer builder
+   */
+  public SerializerBuilder withNamespace(Namespace namespace) {
+    namespaceBuilder.register(namespace);
+    return this;
+  }
+
+  /**
+   * Sets the serializable types.
+   *
+   * @param types the types to register
+   * @return the serializer builder
+   */
+  public SerializerBuilder withTypes(Class<?>... types) {
+    namespaceBuilder.register(types);
+    return this;
+  }
+
+  /**
+   * Adds a serializable type to the builder.
+   *
+   * @param type the type to add
+   * @return the serializer builder
+   */
+  public SerializerBuilder addType(Class<?> type) {
+    namespaceBuilder.register(type);
+    return this;
+  }
+
+  /**
+   * Adds a serializer to the builder.
+   *
+   * @param serializer the serializer to add
+   * @param types the serializable types
+   * @return the serializer builder
+   */
+  public SerializerBuilder addSerializer(com.esotericsoftware.kryo.Serializer serializer, Class<?>... types) {
+    namespaceBuilder.register(serializer, types);
+    return this;
+  }
+
+  @Override
+  public Serializer build() {
+    return Serializer.using(name != null ? namespaceBuilder.build(name) : namespaceBuilder.build());
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/package-info.java
new file mode 100644 (file)
index 0000000..c6f1264
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides classes and interfaces for binary serialization.
+ */
+package io.atomix.utils.serializer;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ArraysAsListSerializer.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ArraysAsListSerializer.java
new file mode 100644 (file)
index 0000000..9e8dbd6
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.atomix.utils.serializer.serializers;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Kryo Serializer for {@link java.util.Arrays#asList(Object...)}.
+ */
+public final class ArraysAsListSerializer extends Serializer<List<?>> {
+
+  @Override
+  public void write(Kryo kryo, Output output, List<?> object) {
+    output.writeInt(object.size(), true);
+    for (Object elm : object) {
+      kryo.writeClassAndObject(output, elm);
+    }
+  }
+
+  @Override
+  public List<?> read(Kryo kryo, Input input, Class<List<?>> type) {
+    final int size = input.readInt(true);
+    List<Object> list = new ArrayList<>(size);
+    for (int i = 0; i < size; ++i) {
+      list.add(kryo.readClassAndObject(input));
+    }
+    return list;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/DefaultSerializers.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/DefaultSerializers.java
new file mode 100644 (file)
index 0000000..849a67c
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer.serializers;
+
+import io.atomix.utils.serializer.Serializer;
+
+/**
+ * Default serializers.
+ */
+public class DefaultSerializers {
+
+  /**
+   * Basic serializer.
+   */
+  public static final Serializer BASIC = Serializer.builder().build();
+
+  private DefaultSerializers() {
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ImmutableListSerializer.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ImmutableListSerializer.java
new file mode 100644 (file)
index 0000000..3853ef6
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer.serializers;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Creates {@link ImmutableList} serializer instance.
+ */
+public class ImmutableListSerializer extends Serializer<ImmutableList<?>> {
+
+  /**
+   * Creates {@link ImmutableList} serializer instance.
+   */
+  public ImmutableListSerializer() {
+    // non-null, immutable
+    super(false, true);
+  }
+
+  @Override
+  public void write(Kryo kryo, Output output, ImmutableList<?> object) {
+    output.writeInt(object.size());
+    for (Object e : object) {
+      kryo.writeClassAndObject(output, e);
+    }
+  }
+
+  @Override
+  public ImmutableList<?> read(Kryo kryo, Input input,
+      Class<ImmutableList<?>> type) {
+    final int size = input.readInt();
+    switch (size) {
+      case 0:
+        return ImmutableList.of();
+      case 1:
+        return ImmutableList.of(kryo.readClassAndObject(input));
+      default:
+        Object[] elms = new Object[size];
+        for (int i = 0; i < size; ++i) {
+          elms[i] = kryo.readClassAndObject(input);
+        }
+        return ImmutableList.copyOf(elms);
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ImmutableMapSerializer.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ImmutableMapSerializer.java
new file mode 100644 (file)
index 0000000..f3b9ce5
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer.serializers;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+
+import java.util.Map.Entry;
+
+/**
+ * Kryo Serializer for {@link ImmutableMap}.
+ */
+public class ImmutableMapSerializer extends Serializer<ImmutableMap<?, ?>> {
+
+  /**
+   * Creates {@link ImmutableMap} serializer instance.
+   */
+  public ImmutableMapSerializer() {
+    // non-null, immutable
+    super(false, true);
+  }
+
+  @Override
+  public void write(Kryo kryo, Output output, ImmutableMap<?, ?> object) {
+    output.writeInt(object.size());
+    for (Entry<?, ?> e : object.entrySet()) {
+      kryo.writeClassAndObject(output, e.getKey());
+      kryo.writeClassAndObject(output, e.getValue());
+    }
+  }
+
+  @Override
+  public ImmutableMap<?, ?> read(Kryo kryo, Input input,
+      Class<ImmutableMap<?, ?>> type) {
+    final int size = input.readInt();
+    switch (size) {
+      case 0:
+        return ImmutableMap.of();
+      case 1:
+        return ImmutableMap.of(kryo.readClassAndObject(input),
+            kryo.readClassAndObject(input));
+
+      default:
+        Builder<Object, Object> builder = ImmutableMap.builder();
+        for (int i = 0; i < size; ++i) {
+          builder.put(kryo.readClassAndObject(input),
+              kryo.readClassAndObject(input));
+        }
+        return builder.build();
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ImmutableSetSerializer.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/ImmutableSetSerializer.java
new file mode 100644 (file)
index 0000000..6887c54
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer.serializers;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Kryo Serializer for {@link ImmutableSet}.
+ */
+public class ImmutableSetSerializer extends Serializer<ImmutableSet<?>> {
+
+  /**
+   * Creates {@link ImmutableSet} serializer instance.
+   */
+  public ImmutableSetSerializer() {
+    // non-null, immutable
+    super(false, true);
+  }
+
+  @Override
+  public void write(Kryo kryo, Output output, ImmutableSet<?> object) {
+    output.writeInt(object.size());
+    for (Object e : object) {
+      kryo.writeClassAndObject(output, e);
+    }
+  }
+
+  @Override
+  public ImmutableSet<?> read(Kryo kryo, Input input,
+      Class<ImmutableSet<?>> type) {
+    final int size = input.readInt();
+    switch (size) {
+      case 0:
+        return ImmutableSet.of();
+      case 1:
+        return ImmutableSet.of(kryo.readClassAndObject(input));
+      default:
+        Object[] elms = new Object[size];
+        for (int i = 0; i < size; ++i) {
+          elms[i] = kryo.readClassAndObject(input);
+        }
+        return ImmutableSet.copyOf(elms);
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/serializer/serializers/package-info.java
new file mode 100644 (file)
index 0000000..c6f3150
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Common serializer implementations.
+ */
+package io.atomix.utils.serializer.serializers;
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Clock.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Clock.java
new file mode 100644 (file)
index 0000000..d807fb5
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+/**
+ * Clock.
+ */
+public interface Clock<T extends Timestamp> {
+
+  /**
+   * Returns the current time of the clock.
+   *
+   * @return the current time
+   */
+  T getTime();
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Epoch.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Epoch.java
new file mode 100644 (file)
index 0000000..34c222b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+/**
+ * Epoch.
+ * <p>
+ * An epoch is a specific type of {@link LogicalTimestamp} that represents a long term section of logical time.
+ */
+public class Epoch extends LogicalTimestamp {
+
+  /**
+   * Returns a new logical timestamp for the given logical time.
+   *
+   * @param value the logical time for which to create a new logical timestamp
+   * @return the logical timestamp
+   */
+  public static Epoch of(long value) {
+    return new Epoch(value);
+  }
+
+  /**
+   * Creates a new epoch timestamp.
+   *
+   * @param value the epoch value
+   */
+  public Epoch(long value) {
+    super(value);
+  }
+
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/LogicalClock.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/LogicalClock.java
new file mode 100644 (file)
index 0000000..10697f9
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Logical clock.
+ */
+public class LogicalClock implements Clock<LogicalTimestamp> {
+  private LogicalTimestamp currentTimestamp;
+
+  public LogicalClock() {
+    this(new LogicalTimestamp(0));
+  }
+
+  public LogicalClock(LogicalTimestamp currentTimestamp) {
+    this.currentTimestamp = currentTimestamp;
+  }
+
+  @Override
+  public LogicalTimestamp getTime() {
+    return currentTimestamp;
+  }
+
+  /**
+   * Increments the clock and returns the new timestamp.
+   *
+   * @return the updated clock time
+   */
+  public LogicalTimestamp increment() {
+    return update(new LogicalTimestamp(currentTimestamp.value() + 1));
+  }
+
+  /**
+   * Updates the clock using the given timestamp.
+   *
+   * @param timestamp the timestamp with which to update the clock
+   * @return the updated clock time
+   */
+  public LogicalTimestamp update(LogicalTimestamp timestamp) {
+    if (timestamp.value() > currentTimestamp.value()) {
+      this.currentTimestamp = timestamp;
+    }
+    return currentTimestamp;
+  }
+
+  /**
+   * Increments the clock and updates it using the given timestamp.
+   *
+   * @param timestamp the timestamp with which to update the clock
+   * @return the updated clock time
+   */
+  public LogicalTimestamp incrementAndUpdate(LogicalTimestamp timestamp) {
+    long nextValue = currentTimestamp.value() + 1;
+    if (timestamp.value() > nextValue) {
+      return update(timestamp);
+    }
+    return increment();
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("time", getTime())
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/LogicalTimestamp.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/LogicalTimestamp.java
new file mode 100644 (file)
index 0000000..3e90e65
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ComparisonChain;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Timestamp based on logical sequence value.
+ * <p>
+ * LogicalTimestamps are ordered by their sequence values.
+ */
+public class LogicalTimestamp implements Timestamp {
+
+  /**
+   * Returns a new logical timestamp for the given logical time.
+   *
+   * @param value the logical time for which to create a new logical timestamp
+   * @return the logical timestamp
+   */
+  public static LogicalTimestamp of(long value) {
+    return new LogicalTimestamp(value);
+  }
+
+  private final long value;
+
+  public LogicalTimestamp(long value) {
+    this.value = value;
+  }
+
+  /**
+   * Returns the sequence value.
+   *
+   * @return sequence value
+   */
+  public long value() {
+    return this.value;
+  }
+
+  /**
+   * Returns the timestamp as a version.
+   *
+   * @return the timestamp as a version
+   */
+  public Version asVersion() {
+    return new Version(value);
+  }
+
+  @Override
+  public int compareTo(Timestamp o) {
+    Preconditions.checkArgument(o instanceof LogicalTimestamp,
+        "Must be LogicalTimestamp", o);
+    LogicalTimestamp that = (LogicalTimestamp) o;
+
+    return ComparisonChain.start()
+        .compare(this.value, that.value)
+        .result();
+  }
+
+  @Override
+  public int hashCode() {
+    return Long.hashCode(value);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof LogicalTimestamp)) {
+      return false;
+    }
+    LogicalTimestamp that = (LogicalTimestamp) obj;
+    return Objects.equals(this.value, that.value);
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(getClass())
+        .add("value", value)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/MultiValuedTimestamp.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/MultiValuedTimestamp.java
new file mode 100644 (file)
index 0000000..c693305
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ComparisonChain;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * A logical timestamp that derives its value from two input values. The first
+ * value always takes precedence over the second value when comparing timestamps.
+ */
+public class MultiValuedTimestamp<T extends Comparable<T>, U extends Comparable<U>> implements Timestamp {
+  private final T value1;
+  private final U value2;
+
+  /**
+   * Creates a new timestamp based on two values. The first value has higher
+   * precedence than the second when comparing timestamps.
+   *
+   * @param value1 first value
+   * @param value2 second value
+   */
+  public MultiValuedTimestamp(T value1, U value2) {
+    this.value1 = Preconditions.checkNotNull(value1);
+    this.value2 = Preconditions.checkNotNull(value2);
+  }
+
+  @Override
+  public int compareTo(Timestamp o) {
+    Preconditions.checkArgument(o instanceof MultiValuedTimestamp,
+        "Must be MultiValuedTimestamp", o);
+    MultiValuedTimestamp that = (MultiValuedTimestamp) o;
+
+    return ComparisonChain.start()
+        .compare(this.value1, that.value1)
+        .compare(this.value2, that.value2)
+        .result();
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(value1, value2);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof MultiValuedTimestamp)) {
+      return false;
+    }
+    MultiValuedTimestamp that = (MultiValuedTimestamp) obj;
+    return Objects.equals(this.value1, that.value1)
+        && Objects.equals(this.value2, that.value2);
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(getClass())
+        .add("value1", value1)
+        .add("value2", value2)
+        .toString();
+  }
+
+  /**
+   * Returns the first value.
+   *
+   * @return first value
+   */
+  public T value1() {
+    return value1;
+  }
+
+  /**
+   * Returns the second value.
+   *
+   * @return second value
+   */
+  public U value2() {
+    return value2;
+  }
+
+  // Default constructor for serialization
+  @SuppressWarnings("unused")
+  private MultiValuedTimestamp() {
+    this.value1 = null;
+    this.value2 = null;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Timestamp.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Timestamp.java
new file mode 100644 (file)
index 0000000..7d452ff
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Opaque version structure.
+ * <p>
+ * Classes implementing this interface must also implement
+ * {@link #hashCode()} and {@link #equals(Object)}.
+ */
+public interface Timestamp extends Comparable<Timestamp> {
+
+  @Override
+  int hashCode();
+
+  @Override
+  boolean equals(Object obj);
+
+  /**
+   * Tests if this timestamp is newer than the specified timestamp.
+   *
+   * @param other timestamp to compare against
+   * @return true if this instance is newer
+   */
+  default boolean isNewerThan(Timestamp other) {
+    return this.compareTo(Preconditions.checkNotNull(other)) > 0;
+  }
+
+  /**
+   * Tests if this timestamp is older than the specified timestamp.
+   *
+   * @param other timestamp to compare against
+   * @return true if this instance is older
+   */
+  default boolean isOlderThan(Timestamp other) {
+    return this.compareTo(Preconditions.checkNotNull(other)) < 0;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/VectorClock.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/VectorClock.java
new file mode 100644 (file)
index 0000000..20c4810
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import com.google.common.annotations.Beta;
+import io.atomix.utils.Identifier;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Vector clock.
+ */
+@Beta
+public class VectorClock<T extends Identifier> implements Clock<VectorTimestamp<T>> {
+  private final T localIdentifier;
+  private final Map<T, VectorTimestamp<T>> vector = new HashMap<>();
+
+  public VectorClock(T localIdentifier) {
+    this(new VectorTimestamp<T>(localIdentifier, 0));
+  }
+
+  public VectorClock(VectorTimestamp<T> localTimestamp) {
+    this(localTimestamp, Collections.emptyList());
+  }
+
+  public VectorClock(VectorTimestamp<T> localTimestamp, Collection<VectorTimestamp<T>> vector) {
+    this.localIdentifier = localTimestamp.identifier();
+    this.vector.put(localTimestamp.identifier(), localTimestamp);
+    for (VectorTimestamp<T> timestamp : vector) {
+      this.vector.put(timestamp.identifier(), timestamp);
+    }
+  }
+
+  @Override
+  public VectorTimestamp<T> getTime() {
+    return vector.get(localIdentifier);
+  }
+
+  /**
+   * Returns the local logical timestamp.
+   *
+   * @return the logical timestamp for the local identifier
+   */
+  public LogicalTimestamp getLocalTimestamp() {
+    return getTime();
+  }
+
+  /**
+   * Returns the logical timestamp for the given identifier.
+   *
+   * @param identifier the identifier for which to return the timestamp
+   * @return the logical timestamp for the given identifier
+   */
+  public LogicalTimestamp getTimestamp(T identifier) {
+    return vector.get(identifier);
+  }
+
+  /**
+   * Returns a collection of identifier-timestamp pairs.
+   *
+   * @return a collection of identifier-timestamp pairs
+   */
+  public Collection<VectorTimestamp<T>> getTimestamps() {
+    return vector.values();
+  }
+
+  /**
+   * Updates the given timestamp.
+   *
+   * @param timestamp the timestamp to update
+   */
+  public void update(VectorTimestamp<T> timestamp) {
+    VectorTimestamp<T> currentTimestamp = vector.get(timestamp.identifier());
+    if (currentTimestamp == null || currentTimestamp.value() < timestamp.value()) {
+      vector.put(timestamp.identifier(), timestamp);
+    }
+  }
+
+  /**
+   * Updates the vector clock.
+   *
+   * @param clock the vector clock with which to update this clock
+   */
+  public void update(VectorClock<T> clock) {
+    for (VectorTimestamp<T> timestamp : clock.vector.values()) {
+      update(timestamp);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("time", getTime())
+        .add("vector", getTimestamps())
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/VectorTimestamp.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/VectorTimestamp.java
new file mode 100644 (file)
index 0000000..f2eb852
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import com.google.common.collect.ComparisonChain;
+import io.atomix.utils.Identifier;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Vector clock timestamp.
+ */
+public class VectorTimestamp<T extends Identifier> extends LogicalTimestamp {
+  private final T identifier;
+
+  public VectorTimestamp(T identifier, long value) {
+    super(value);
+    this.identifier = identifier;
+  }
+
+  /**
+   * Returns the timestamp identifier.
+   *
+   * @return the timestamp identifier
+   */
+  public T identifier() {
+    return identifier;
+  }
+
+  @Override
+  public int compareTo(Timestamp o) {
+    checkArgument(o instanceof VectorTimestamp, "Must be VectorTimestamp", o);
+    VectorTimestamp that = (VectorTimestamp) o;
+
+    return ComparisonChain.start()
+        .compare(this.identifier.id(), that.identifier.id())
+        .compare(this.value(), that.value())
+        .result();
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(identifier(), value());
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof VectorTimestamp)) {
+      return false;
+    }
+    VectorTimestamp that = (VectorTimestamp) obj;
+    return Objects.equals(this.identifier, that.identifier)
+        && Objects.equals(this.value(), that.value());
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("identifier", identifier())
+        .add("value", value())
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Version.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Version.java
new file mode 100644 (file)
index 0000000..724c25f
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import com.google.common.collect.ComparisonChain;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Logical timestamp for versions.
+ * <p>
+ * The version is a logical timestamp that represents a point in logical time at which an event occurs.
+ * This is used in both pessimistic and optimistic locking protocols to ensure that the state of a shared resource
+ * has not changed at the end of a transaction.
+ */
+public class Version implements Timestamp {
+  private final long version;
+
+  public Version(long version) {
+    this.version = version;
+  }
+
+  /**
+   * Returns the version.
+   *
+   * @return the version
+   */
+  public long value() {
+    return this.version;
+  }
+
+  @Override
+  public int compareTo(Timestamp o) {
+    checkArgument(o instanceof Version,
+        "Must be LockVersion", o);
+    Version that = (Version) o;
+
+    return ComparisonChain.start()
+        .compare(this.version, that.version)
+        .result();
+  }
+
+  @Override
+  public int hashCode() {
+    return Long.hashCode(version);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof Version)) {
+      return false;
+    }
+    Version that = (Version) obj;
+    return Objects.equals(this.version, that.version);
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(getClass())
+        .add("version", version)
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Versioned.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/Versioned.java
new file mode 100644 (file)
index 0000000..17cbbc9
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.atomix.utils.time;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import io.atomix.utils.misc.ArraySizeHashPrinter;
+import io.atomix.utils.misc.TimestampPrinter;
+
+import java.util.function.Function;
+
+/**
+ * Versioned value.
+ *
+ * @param <V> value type.
+ */
+public class Versioned<V> {
+  private final V value;
+  private final long version;
+  private final long creationTime;
+
+  /**
+   * Constructs a new versioned value.
+   *
+   * @param value        value
+   * @param version      version
+   * @param creationTime milliseconds of the creation event
+   *                     from the Java epoch of 1970-01-01T00:00:00Z
+   */
+  public Versioned(V value, long version, long creationTime) {
+    this.value = value;
+    this.version = version;
+    this.creationTime = creationTime;
+  }
+
+  /**
+   * Constructs a new versioned value.
+   *
+   * @param value   value
+   * @param version version
+   */
+  public Versioned(V value, long version) {
+    this(value, version, System.currentTimeMillis());
+  }
+
+  /**
+   * Returns the value.
+   *
+   * @return value.
+   */
+  public V value() {
+    return value;
+  }
+
+  /**
+   * Returns the version.
+   *
+   * @return version
+   */
+  public long version() {
+    return version;
+  }
+
+  /**
+   * Returns the system time when this version was created.
+   * <p>
+   * Care should be taken when relying on creationTime to
+   * implement any behavior in a distributed setting. Due
+   * to the possibility of clock skew it is likely that
+   * even creationTimes of causally related versions can be
+   * out or order.
+   *
+   * @return creation time
+   */
+  public long creationTime() {
+    return creationTime;
+  }
+
+  /**
+   * Maps this instance into another after transforming its
+   * value while retaining the same version and creationTime.
+   *
+   * @param transformer function for mapping the value
+   * @param <U>         value type of the returned instance
+   * @return mapped instance
+   */
+  public synchronized <U> Versioned<U> map(Function<V, U> transformer) {
+    return new Versioned<>(value != null ? transformer.apply(value) : null, version, creationTime);
+  }
+
+  /**
+   * Returns the value of the specified Versioned object if non-null or else returns
+   * a default value.
+   *
+   * @param versioned    versioned object
+   * @param defaultValue default value to return if versioned object is null
+   * @param <U>          type of the versioned value
+   * @return versioned value or default value if versioned object is null
+   */
+  public static <U> U valueOrElse(Versioned<U> versioned, U defaultValue) {
+    return versioned == null ? defaultValue : versioned.value();
+  }
+
+  /**
+   * Returns the value of the specified Versioned object if non-null or else returns null.
+   *
+   * @param versioned versioned object
+   * @param <U>       type of the versioned value
+   * @return versioned value or null if versioned object is null
+   */
+  public static <U> U valueOrNull(Versioned<U> versioned) {
+    return valueOrElse(versioned, null);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(value, version, creationTime);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (!(other instanceof Versioned)) {
+      return false;
+    }
+    Versioned<V> that = (Versioned) other;
+    return Objects.equal(this.value, that.value)
+        && Objects.equal(this.version, that.version)
+        && Objects.equal(this.creationTime, that.creationTime);
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("value", value instanceof byte[] ? ArraySizeHashPrinter.of((byte[]) value) : value)
+        .add("version", version)
+        .add("creationTime", new TimestampPrinter(creationTime))
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/WallClock.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/WallClock.java
new file mode 100644 (file)
index 0000000..6390862
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Wall clock.
+ */
+public class WallClock implements Clock<WallClockTimestamp> {
+  @Override
+  public WallClockTimestamp getTime() {
+    return new WallClockTimestamp();
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("time", getTime())
+        .toString();
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/WallClockTimestamp.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/WallClockTimestamp.java
new file mode 100644 (file)
index 0000000..abd9b8a
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import com.google.common.collect.ComparisonChain;
+import io.atomix.utils.misc.TimestampPrinter;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * A Timestamp that derives its value from the prevailing
+ * wallclock time on the controller where it is generated.
+ */
+public class WallClockTimestamp implements Timestamp {
+
+  /**
+   * Returns a new wall clock timestamp for the given unix timestamp.
+   *
+   * @param unixTimestamp the unix timestamp for which to create a new wall clock timestamp
+   * @return the wall clock timestamp
+   */
+  public static WallClockTimestamp from(long unixTimestamp) {
+    return new WallClockTimestamp(unixTimestamp);
+  }
+
+  private final long unixTimestamp;
+
+  public WallClockTimestamp() {
+    unixTimestamp = System.currentTimeMillis();
+  }
+
+  public WallClockTimestamp(long timestamp) {
+    unixTimestamp = timestamp;
+  }
+
+  @Override
+  public int compareTo(Timestamp o) {
+    checkArgument(o instanceof WallClockTimestamp,
+        "Must be WallClockTimestamp", o);
+    WallClockTimestamp that = (WallClockTimestamp) o;
+
+    return ComparisonChain.start()
+        .compare(this.unixTimestamp, that.unixTimestamp)
+        .result();
+  }
+
+  @Override
+  public int hashCode() {
+    return Long.hashCode(unixTimestamp);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof WallClockTimestamp)) {
+      return false;
+    }
+    WallClockTimestamp that = (WallClockTimestamp) obj;
+    return Objects.equals(this.unixTimestamp, that.unixTimestamp);
+  }
+
+  @Override
+  public String toString() {
+    return new TimestampPrinter(unixTimestamp).toString();
+  }
+
+  /**
+   * Returns the unixTimestamp.
+   *
+   * @return unix timestamp
+   */
+  public long unixTimestamp() {
+    return unixTimestamp;
+  }
+}
diff --git a/third-party/atomix/utils/src/main/java/io/atomix/utils/time/package-info.java b/third-party/atomix/utils/src/main/java/io/atomix/utils/time/package-info.java
new file mode 100644 (file)
index 0000000..bd0beef
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Provides classes and interfaces for representing and operating on both logical and physical representations of time.
+ */
+package io.atomix.utils.time;
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/ArraySizeHashPrinterTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/ArraySizeHashPrinterTest.java
new file mode 100644 (file)
index 0000000..404a97e
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import io.atomix.utils.misc.ArraySizeHashPrinter;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Array size hash printer test.
+ */
+public class ArraySizeHashPrinterTest {
+  @Test
+  public void testArraySizeHashPrinter() throws Exception {
+    ArraySizeHashPrinter printer = ArraySizeHashPrinter.of(new byte[]{1, 2, 3});
+    assertEquals("byte[]{length=3, hash=30817}", printer.toString());
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/GenericsTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/GenericsTest.java
new file mode 100644 (file)
index 0000000..bda195e
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Generics test.
+ */
+public class GenericsTest {
+  @Test
+  public void testGetInterfaceType() throws Exception {
+    assertEquals(String.class, Generics.getGenericInterfaceType(new ConcreteInterface(), GenericInterface.class, 0));
+    assertEquals(SomeClass.class, Generics.getGenericInterfaceType(new ConcreteInterface(), GenericInterface.class, 1));
+  }
+
+  @Test
+  public void testGetClassType() throws Exception {
+    assertEquals(SomeClass.class, Generics.getGenericClassType(new ConcreteClass(), GenericClass.class, 0));
+  }
+
+  public interface GenericInterface<T1, T2> {
+    T1 type1();
+
+    T2 type2();
+  }
+
+  public static class ConcreteInterface implements GenericInterface<String, SomeClass> {
+    @Override
+    public String type1() {
+      return null;
+    }
+
+    @Override
+    public SomeClass type2() {
+      return null;
+    }
+  }
+
+  public abstract class GenericClass<T> {
+    public abstract T type();
+  }
+
+  public class ConcreteClass extends GenericClass<SomeClass> {
+    @Override
+    public SomeClass type() {
+      return null;
+    }
+  }
+
+  public class SomeClass {
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/MatchTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/MatchTest.java
new file mode 100644 (file)
index 0000000..033b75a
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import com.google.common.base.Objects;
+import io.atomix.utils.misc.Match;
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+/**
+ * Unit tests for Match.
+ */
+public class MatchTest {
+
+  @Test
+  public void testMatches() {
+    Match<String> m1 = Match.any();
+    assertTrue(m1.matches(null));
+    assertTrue(m1.matches("foo"));
+    assertTrue(m1.matches("bar"));
+
+    Match<String> m2 = Match.ifNull();
+    assertTrue(m2.matches(null));
+    assertFalse(m2.matches("foo"));
+
+    Match<String> m3 = Match.ifValue("foo");
+    assertFalse(m3.matches(null));
+    assertFalse(m3.matches("bar"));
+    assertTrue(m3.matches("foo"));
+
+    Match<byte[]> m4 = Match.ifValue(new byte[8]);
+    assertTrue(m4.matches(new byte[8]));
+    assertFalse(m4.matches(new byte[7]));
+  }
+
+  @Test
+  public void testEquals() {
+    Match<String> m1 = Match.any();
+    Match<String> m2 = Match.any();
+    Match<String> m3 = Match.ifNull();
+    Match<String> m4 = Match.ifValue("bar");
+    assertEquals(m1, m2);
+    assertFalse(Objects.equal(m1, m3));
+    assertFalse(Objects.equal(m3, m4));
+    Object o = new Object();
+    assertFalse(Objects.equal(m1, o));
+  }
+
+  @Test
+  public void testMap() {
+    Match<String> m1 = Match.ifNull();
+    assertEquals(m1.map(s -> "bar"), Match.ifNull());
+    Match<String> m2 = Match.ifValue("foo");
+    Match<String> m3 = m2.map(s -> "bar");
+    assertTrue(m3.matches("bar"));
+  }
+
+  @Test
+  public void testIfNotNull() {
+    Match<String> m = Match.ifNotNull();
+    assertFalse(m.matches(null));
+    assertTrue(m.matches("foo"));
+  }
+
+  @Test
+  public void testIfNotValue() {
+    Match<String> m1 = Match.ifNotValue(null);
+    Match<String> m2 = Match.ifNotValue("foo");
+    assertFalse(m1.matches(null));
+    assertFalse(m2.matches("foo"));
+  }
+
+  @Test
+  public void testToString() {
+    Match<String> m1 = Match.any();
+    Match<String> m2 = Match.any();
+    Match<String> m3 = Match.ifValue("foo");
+    Match<String> m4 = Match.ifValue("foo");
+    Match<String> m5 = Match.ifNotValue("foo");
+
+    String note = "Results of toString() should be consistent -- ";
+
+    assertTrue(note, m1.toString().equals(m2.toString()));
+    assertTrue(note, m3.toString().equals(m4.toString()));
+    assertFalse(note, m4.toString().equals(m5.toString()));
+  }
+
+  @Test
+  public void testHashCode() {
+    Match<String> m1 = Match.ifValue("foo");
+    Match<String> m2 = Match.ifNotValue("foo");
+    Match<String> m3 = Match.ifValue("foo");
+    Match<String> m4 = Match.ifNotNull();
+    Match<String> m5 = Match.ifNull();
+
+    assertTrue(m1.hashCode() == m3.hashCode());
+    assertFalse(m2.hashCode() == m1.hashCode());
+    assertFalse(m4.hashCode() == m5.hashCode());
+
+  }
+
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/TimestampPrinterTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/TimestampPrinterTest.java
new file mode 100644 (file)
index 0000000..49f2875
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import io.atomix.utils.misc.TimestampPrinter;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Timestamp printer test.
+ */
+public class TimestampPrinterTest {
+  @Test
+  @Ignore // Timestamp is environment specific
+  public void testTimestampPrinter() throws Exception {
+    TimestampPrinter printer = TimestampPrinter.of(1);
+    assertEquals("1969-12-31 04:00:00,001", printer.toString());
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/VersionTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/VersionTest.java
new file mode 100644 (file)
index 0000000..5416749
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Version test.
+ */
+public class VersionTest {
+  @Test
+  public void testVersionComparison() {
+    assertTrue(Version.from("1.0.0").compareTo(Version.from("2.0.0")) < 0);
+    assertTrue(Version.from("2.0.0").compareTo(Version.from("1.0.0")) > 0);
+    assertTrue(Version.from("1.0.0").compareTo(Version.from("0.1.0")) > 0);
+    assertTrue(Version.from("0.1.0").compareTo(Version.from("1.0.0")) < 0);
+    assertTrue(Version.from("0.1.0").compareTo(Version.from("0.1.1")) < 0);
+    assertTrue(Version.from("1.0.0").compareTo(Version.from("0.0.1")) > 0);
+    assertTrue(Version.from("1.1.1").compareTo(Version.from("1.0.3")) > 0);
+    assertTrue(Version.from("1.0.0").compareTo(Version.from("1.0.0-beta1")) > 0);
+    assertTrue(Version.from("1.0.0-rc2").compareTo(Version.from("1.0.0-rc1")) > 0);
+    assertTrue(Version.from("1.0.0-rc1").compareTo(Version.from("1.0.0-beta1")) > 0);
+    assertTrue(Version.from("2.0.0-beta1").compareTo(Version.from("1.0.0")) > 0);
+    assertTrue(Version.from("1.0.0-alpha1").compareTo(Version.from("1.0.0-SNAPSHOT")) > 0);
+  }
+
+  @Test
+  public void testVersionToString() {
+    assertEquals("1.0.0", Version.from("1.0.0").toString());
+    assertEquals("1.0.0-alpha1", Version.from("1.0.0-alpha1").toString());
+    assertEquals("1.0.0-beta1", Version.from("1.0.0-beta1").toString());
+    assertEquals("1.0.0-rc1", Version.from("1.0.0-rc1").toString());
+    assertEquals("1.0.0-SNAPSHOT", Version.from("1.0.0-SNAPSHOT").toString());
+  }
+
+  @Test
+  public void testInvalidVersions() {
+    assertIllegalArgument(() -> Version.from("1"));
+    assertIllegalArgument(() -> Version.from("1.0"));
+    assertIllegalArgument(() -> Version.from("1.0-beta1"));
+    assertIllegalArgument(() -> Version.from("1.0.0.0"));
+    assertIllegalArgument(() -> Version.from("1.0.0.0-beta1"));
+    assertIllegalArgument(() -> Version.from("1.0.0-not1"));
+    assertIllegalArgument(() -> Version.from("1.0.0-alpha"));
+    assertIllegalArgument(() -> Version.from("1.0.0-beta"));
+    assertIllegalArgument(() -> Version.from("1.0.0-rc"));
+  }
+
+  private void assertIllegalArgument(Runnable callback) {
+    try {
+      callback.run();
+      fail();
+    } catch (IllegalArgumentException e) {
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/concurrent/OrderedFutureTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/concurrent/OrderedFutureTest.java
new file mode 100644 (file)
index 0000000..dfada0c
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Ordered completable future test.
+ */
+public class OrderedFutureTest {
+
+  /**
+   * Tests ordered completion of future callbacks.
+   */
+  @Test
+  public void testOrderedCompletion() throws Throwable {
+    CompletableFuture<String> future = new OrderedFuture<>();
+    AtomicInteger order = new AtomicInteger();
+    future.whenComplete((r, e) -> assertEquals(1, order.incrementAndGet()));
+    future.whenComplete((r, e) -> assertEquals(2, order.incrementAndGet()));
+    future.handle((r, e) -> {
+      assertEquals(3, order.incrementAndGet());
+      assertEquals("foo", r);
+      return "bar";
+    });
+    future.thenRun(() -> assertEquals(3, order.incrementAndGet()));
+    future.thenAccept(r -> {
+      assertEquals(5, order.incrementAndGet());
+      assertEquals("foo", r);
+    });
+    future.thenApply(r -> {
+      assertEquals(6, order.incrementAndGet());
+      assertEquals("foo", r);
+      return "bar";
+    });
+    future.whenComplete((r, e) -> {
+      assertEquals(7, order.incrementAndGet());
+      assertEquals("foo", r);
+    });
+    future.complete("foo");
+  }
+
+  /**
+   * Tests ordered failure of future callbacks.
+   */
+  public void testOrderedFailure() throws Throwable {
+    CompletableFuture<String> future = new OrderedFuture<>();
+    AtomicInteger order = new AtomicInteger();
+    future.whenComplete((r, e) -> assertEquals(1, order.incrementAndGet()));
+    future.whenComplete((r, e) -> assertEquals(2, order.incrementAndGet()));
+    future.handle((r, e) -> {
+      assertEquals(3, order.incrementAndGet());
+      return "bar";
+    });
+    future.thenRun(() -> fail());
+    future.thenAccept(r -> fail());
+    future.exceptionally(e -> {
+      assertEquals(3, order.incrementAndGet());
+      return "bar";
+    });
+    future.completeExceptionally(new RuntimeException("foo"));
+  }
+
+  /**
+   * Tests calling callbacks that are added after completion.
+   */
+  public void testAfterComplete() throws Throwable {
+    CompletableFuture<String> future = new OrderedFuture<>();
+    future.whenComplete((result, error) -> assertEquals("foo", result));
+    future.complete("foo");
+    AtomicInteger count = new AtomicInteger();
+    future.whenComplete((result, error) -> {
+      assertEquals("foo", result);
+      assertEquals(1, count.incrementAndGet());
+    });
+    future.thenAccept(result -> {
+      assertEquals("foo", result);
+      assertEquals(2, count.incrementAndGet());
+    });
+    assertEquals(2, count.get());
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/concurrent/RetryingFunctionTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/concurrent/RetryingFunctionTest.java
new file mode 100644 (file)
index 0000000..8d70ccc
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.concurrent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Retrying function test.
+ */
+public class RetryingFunctionTest {
+  private int round;
+
+  @Before
+  public void setUp() {
+    round = 1;
+  }
+
+  @After
+  public void tearDown() {
+    round = 0;
+  }
+
+  @Test(expected = RetryableException.class)
+  public void testNoRetries() {
+    new RetryingFunction<>(this::succeedAfterOneFailure, RetryableException.class, 0, 10).apply(null);
+  }
+
+  @Test
+  public void testSuccessAfterOneRetry() {
+    new RetryingFunction<>(this::succeedAfterOneFailure, RetryableException.class, 1, 10).apply(null);
+  }
+
+  @Test(expected = RetryableException.class)
+  public void testFailureAfterOneRetry() {
+    new RetryingFunction<>(this::succeedAfterTwoFailures, RetryableException.class, 1, 10).apply(null);
+  }
+
+  @Test
+  public void testFailureAfterTwoRetries() {
+    new RetryingFunction<>(this::succeedAfterTwoFailures, RetryableException.class, 2, 10).apply(null);
+  }
+
+  @Test(expected = NonRetryableException.class)
+  public void testFailureWithNonRetryableFailure() {
+    new RetryingFunction<>(this::failCompletely, RetryableException.class, 2, 10).apply(null);
+  }
+
+  private String succeedAfterOneFailure(String input) {
+    if (round++ <= 1) {
+      throw new RetryableException();
+    } else {
+      return "pass";
+    }
+  }
+
+  private String succeedAfterTwoFailures(String input) {
+    if (round++ <= 2) {
+      throw new RetryableException();
+    } else {
+      return "pass";
+    }
+  }
+
+  private String failCompletely(String input) {
+    if (round++ <= 1) {
+      throw new NonRetryableException();
+    } else {
+      return "pass";
+    }
+  }
+
+  private static class RetryableException extends RuntimeException {
+  }
+
+  private static class NonRetryableException extends RuntimeException {
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/logging/LoggerContextTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/logging/LoggerContextTest.java
new file mode 100644 (file)
index 0000000..df4ae40
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.logging;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Contextual logger test.
+ */
+public class LoggerContextTest {
+  @Test
+  public void testLoggerContext() throws Exception {
+    LoggerContext context = LoggerContext.builder("test")
+        .addValue(1)
+        .add("foo", "bar")
+        .build();
+    assertEquals("test{1}{foo=bar}", context.toString());
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/misc/StringUtilsTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/misc/StringUtilsTest.java
new file mode 100644 (file)
index 0000000..ce43e55
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.misc;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class StringUtilsTest {
+
+  @Test
+  public void testNull() {
+    assertNull(StringUtils.split(null, ","));
+  }
+
+  @Test
+  public void testFilter() {
+    String[] result = StringUtils.split("1,  ,,", ",");
+    assertNotNull(result);
+    assertEquals(1, result.length);
+    assertEquals("1", result[0]);
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/net/AddressTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/net/AddressTest.java
new file mode 100644 (file)
index 0000000..f40fc6d
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.net;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Address test.
+ */
+public class AddressTest {
+  @Test
+  public void testIPv4Address() throws Exception {
+    Address address = Address.from("127.0.0.1:5000");
+    assertEquals("127.0.0.1", address.host());
+    assertEquals(5000, address.port());
+    assertEquals("localhost", address.address().getHostName());
+    assertEquals("127.0.0.1:5000", address.toString());
+  }
+
+  @Test
+  public void testIPv6Address() throws Exception {
+    Address address = Address.from("[fe80:cd00:0000:0cde:1257:0000:211e:729c]:5000");
+    assertEquals("fe80:cd00:0000:0cde:1257:0000:211e:729c", address.host());
+    assertEquals(5000, address.port());
+    assertEquals("fe80:cd00:0:cde:1257:0:211e:729c", address.address().getHostName());
+    assertEquals("[fe80:cd00:0000:0cde:1257:0000:211e:729c]:5000", address.toString());
+  }
+
+  @Test
+  @Ignore
+  public void testResolveAddress() throws Exception {
+    Address address = Address.from("localhost", 5000);
+    assertEquals("127.0.0.1", address.address().getHostAddress());
+    assertEquals(5000, address.port());
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/serializer/BufferAwareByteArrayOutputStreamTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/serializer/BufferAwareByteArrayOutputStreamTest.java
new file mode 100644 (file)
index 0000000..697fe82
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class BufferAwareByteArrayOutputStreamTest {
+
+  @Test
+  public void testBufferSize() throws Exception {
+    BufferAwareByteArrayOutputStream outputStream = new BufferAwareByteArrayOutputStream(8);
+    assertEquals(8, outputStream.getBufferSize());
+    outputStream.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
+    assertEquals(8, outputStream.getBufferSize());
+    outputStream.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
+    assertEquals(16, outputStream.getBufferSize());
+    outputStream.reset();
+    assertEquals(16, outputStream.getBufferSize());
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/serializer/KryoInputPoolTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/serializer/KryoInputPoolTest.java
new file mode 100644 (file)
index 0000000..e6dcdc5
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import com.esotericsoftware.kryo.io.Input;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class KryoInputPoolTest {
+
+  private KryoInputPool kryoInputPool;
+
+  @Before
+  public void setUp() throws Exception {
+    kryoInputPool = new KryoInputPool();
+  }
+
+  @Test
+  public void discardOutput() {
+    final Input[] result = new Input[2];
+    kryoInputPool.run(input -> {
+      result[0] = input;
+      return null;
+    }, KryoInputPool.MAX_POOLED_BUFFER_SIZE + 1);
+    kryoInputPool.run(input -> {
+      result[1] = input;
+      return null;
+    }, 0);
+    assertTrue(result[0] != result[1]);
+  }
+
+  @Test
+  public void recycleOutput() {
+    final Input[] result = new Input[2];
+    kryoInputPool.run(input -> {
+      assertEquals(0, input.position());
+      byte[] payload = new byte[]{1, 2, 3, 4};
+      input.setBuffer(payload);
+      assertArrayEquals(payload, input.readBytes(4));
+      result[0] = input;
+      return null;
+    }, 0);
+    assertNull(result[0].getInputStream());
+    assertEquals(0, result[0].position());
+    kryoInputPool.run(input -> {
+      result[1] = input;
+      return null;
+    }, 0);
+    assertTrue(result[0] == result[1]);
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/serializer/KryoOutputPoolTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/serializer/KryoOutputPoolTest.java
new file mode 100644 (file)
index 0000000..af53626
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.serializer;
+
+import com.esotericsoftware.kryo.io.Output;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class KryoOutputPoolTest {
+
+  private KryoOutputPool kryoOutputPool;
+
+  @Before
+  public void setUp() throws Exception {
+    kryoOutputPool = new KryoOutputPool();
+  }
+
+  @Test
+  public void discardOutput() {
+    final Output[] result = new Output[2];
+    kryoOutputPool.run(output -> {
+      result[0] = output;
+      return null;
+    }, KryoOutputPool.MAX_POOLED_BUFFER_SIZE + 1);
+    kryoOutputPool.run(output -> {
+      result[1] = output;
+      return null;
+    }, 0);
+    assertTrue(result[0] != result[1]);
+  }
+
+  @Test
+  public void recycleOutput() {
+    final ByteArrayOutput[] result = new ByteArrayOutput[2];
+    kryoOutputPool.run(output -> {
+      output.writeInt(1);
+      assertEquals(Integer.BYTES, output.position());
+      result[0] = output;
+      return null;
+    }, 0);
+    assertEquals(0, result[0].position());
+    assertEquals(0, result[0].getByteArrayOutputStream().size());
+    kryoOutputPool.run(output -> {
+      assertEquals(0, output.position());
+      result[1] = output;
+      return null;
+    }, 0);
+    assertTrue(result[0] == result[1]);
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/time/EpochTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/time/EpochTest.java
new file mode 100644 (file)
index 0000000..6f367e7
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Logical timestamp test.
+ */
+public class EpochTest {
+  @Test
+  public void testLogicalTimestamp() throws Exception {
+    Epoch epoch = Epoch.of(1);
+    assertEquals(1, epoch.value());
+    assertTrue(epoch.isNewerThan(Epoch.of(0)));
+    assertFalse(epoch.isNewerThan(Epoch.of(2)));
+    assertTrue(epoch.isOlderThan(Epoch.of(2)));
+    assertFalse(epoch.isOlderThan(Epoch.of(0)));
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/time/LogicalClockTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/time/LogicalClockTest.java
new file mode 100644 (file)
index 0000000..b2bf195
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Logical clock test.
+ */
+public class LogicalClockTest {
+  @Test
+  public void testLogicalClock() throws Exception {
+    LogicalClock clock = new LogicalClock();
+    assertEquals(1, clock.increment().value());
+    assertEquals(1, clock.getTime().value());
+    assertEquals(2, clock.increment().value());
+    assertEquals(2, clock.getTime().value());
+    assertEquals(5, clock.update(LogicalTimestamp.of(5)).value());
+    assertEquals(5, clock.getTime().value());
+    assertEquals(5, clock.update(LogicalTimestamp.of(3)).value());
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/time/LogicalTimestampTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/time/LogicalTimestampTest.java
new file mode 100644 (file)
index 0000000..6c7b6a7
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Logical timestamp test.
+ */
+public class LogicalTimestampTest {
+  @Test
+  public void testLogicalTimestamp() throws Exception {
+    LogicalTimestamp timestamp = LogicalTimestamp.of(1);
+    assertEquals(1, timestamp.value());
+    assertTrue(timestamp.isNewerThan(LogicalTimestamp.of(0)));
+    assertFalse(timestamp.isNewerThan(LogicalTimestamp.of(2)));
+    assertTrue(timestamp.isOlderThan(LogicalTimestamp.of(2)));
+    assertFalse(timestamp.isOlderThan(LogicalTimestamp.of(0)));
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/time/MultiValuedTimestampTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/time/MultiValuedTimestampTest.java
new file mode 100644 (file)
index 0000000..e0ea6ad
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * MultiValuedTimestamp unit tests.
+ */
+public class MultiValuedTimestampTest {
+  private final MultiValuedTimestamp<Integer, Integer> stats1 = new MultiValuedTimestamp<>(1, 3);
+  private final MultiValuedTimestamp<Integer, Integer> stats2 = new MultiValuedTimestamp<>(1, 2);
+
+  /**
+   * Tests the creation of the MapEvent object.
+   */
+  @Test
+  public void testConstruction() {
+    assertThat(stats1.value1(), is(1));
+    assertThat(stats1.value2(), is(3));
+  }
+
+  /**
+   * Tests the toCompare function.
+   */
+  @Test
+  public void testToCompare() {
+    assertThat(stats1.compareTo(stats2), is(1));
+  }
+
+  /**
+   * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+   */
+  @Test
+  public void testEquals() {
+    new EqualsTester()
+        .addEqualityGroup(stats1, stats1)
+        .addEqualityGroup(stats2)
+        .testEquals();
+  }
+
+  /**
+   * Tests that the empty argument list constructor for serialization
+   * is present and creates a proper object.
+   */
+  @Test
+  public void testSerializerConstructor() {
+    try {
+      Constructor[] constructors = MultiValuedTimestamp.class.getDeclaredConstructors();
+      assertThat(constructors, notNullValue());
+      Arrays.stream(constructors).filter(ctor ->
+          ctor.getParameterTypes().length == 0)
+          .forEach(noParamsCtor -> {
+            try {
+              noParamsCtor.setAccessible(true);
+              MultiValuedTimestamp stats =
+                  (MultiValuedTimestamp) noParamsCtor.newInstance();
+              assertThat(stats, notNullValue());
+            } catch (Exception e) {
+              Assert.fail("Exception instantiating no parameters constructor");
+            }
+          });
+    } catch (Exception e) {
+      Assert.fail("Exception looking up constructors");
+    }
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/time/VersionTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/time/VersionTest.java
new file mode 100644 (file)
index 0000000..f735348
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Version test.
+ */
+public class VersionTest {
+  @Test
+  public void testVersion() {
+    Version version1 = new Version(1);
+    Version version2 = new Version(1);
+    assertTrue(version1.equals(version2));
+    assertTrue(version1.hashCode() == version2.hashCode());
+    assertTrue(version1.value() == version2.value());
+
+    Version version3 = new Version(2);
+    assertFalse(version1.equals(version3));
+    assertFalse(version1.hashCode() == version3.hashCode());
+    assertFalse(version1.value() == version3.value());
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/time/VersionedTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/time/VersionedTest.java
new file mode 100644 (file)
index 0000000..339b832
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Versioned unit tests.
+ */
+public class VersionedTest {
+
+  private final Versioned<Integer> stats1 = new Versioned<>(1, 2, 3);
+
+  private final Versioned<Integer> stats2 = new Versioned<>(1, 2);
+
+  /**
+   * Tests the creation of the MapEvent object.
+   */
+  @Test
+  public void testConstruction() {
+    assertThat(stats1.value(), is(1));
+    assertThat(stats1.version(), is(2L));
+    assertThat(stats1.creationTime(), is(3L));
+  }
+
+  /**
+   * Maps an Integer to a String - Utility function to test the map function.
+   *
+   * @param a Actual Integer parameter.
+   * @return String Mapped valued.
+   */
+  public static String transform(Integer a) {
+    return Integer.toString(a);
+  }
+
+  /**
+   * Tests the map function.
+   */
+  @Test
+  public void testMap() {
+    Versioned<String> tempObj = stats1.map(VersionedTest::transform);
+    assertThat(tempObj.value(), is("1"));
+  }
+
+  /**
+   * Tests the valueOrElse method.
+   */
+  @Test
+  public void testOrElse() {
+    Versioned<String> vv = new Versioned<>("foo", 1);
+    Versioned<String> nullVV = null;
+    assertThat(Versioned.valueOrElse(vv, "bar"), is("foo"));
+    assertThat(Versioned.valueOrElse(nullVV, "bar"), is("bar"));
+  }
+
+  /**
+   * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+   */
+  @Test
+  public void testEquals() {
+    new EqualsTester()
+        .addEqualityGroup(stats1, stats1)
+        .addEqualityGroup(stats2)
+        .testEquals();
+  }
+
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/time/WallClockTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/time/WallClockTest.java
new file mode 100644 (file)
index 0000000..bc512bc
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Wall clock test.
+ */
+public class WallClockTest {
+  @Test
+  public void testWallClock() throws Exception {
+    WallClock clock = new WallClock();
+    WallClockTimestamp time = clock.getTime();
+    assertNotNull(time);
+    Thread.sleep(5);
+    assertTrue(clock.getTime().unixTimestamp() > time.unixTimestamp());
+  }
+}
diff --git a/third-party/atomix/utils/src/test/java/io/atomix/utils/time/WallClockTimestampTest.java b/third-party/atomix/utils/src/test/java/io/atomix/utils/time/WallClockTimestampTest.java
new file mode 100644 (file)
index 0000000..42eb83e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.atomix.utils.time;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for {@link WallClockTimestamp}.
+ */
+public class WallClockTimestampTest {
+  @Test
+  public final void testBasic() throws InterruptedException {
+    WallClockTimestamp ts1 = new WallClockTimestamp();
+    Thread.sleep(50);
+    WallClockTimestamp ts2 = new WallClockTimestamp();
+    long stamp = System.currentTimeMillis() + 10000;
+    WallClockTimestamp ts3 = new WallClockTimestamp(stamp);
+
+    assertTrue(ts1.compareTo(ts1) == 0);
+    assertTrue(ts2.compareTo(ts1) > 0);
+    assertTrue(ts1.compareTo(ts2) < 0);
+    assertTrue(ts3.unixTimestamp() == stamp);
+  }
+}