diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index dad9160e4..fc3e7364e 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -163,6 +163,8 @@ add_unittest(stream user_data_file peek_skip_1 peek_skip_2 + peek_back_skip + peek_back_throw backwards_compressed extend_compressed truncate_compressed diff --git a/test/unit/test_stream.cpp b/test/unit/test_stream.cpp index 61cafb72d..962983e22 100644 --- a/test/unit/test_stream.cpp +++ b/test/unit/test_stream.cpp @@ -769,6 +769,54 @@ bool peek_skip_test_2() { return true; } +bool peek_back_skip_test() { + tpie::file_stream s; + s.open(); + for (size_t i = 0; i < 1000000; ++i) s.write(i); + + s.seek(42); + TEST_ENSURE(s.peek_back() == 41, "peek_back() wrong"); + + s.seek(100); + TEST_ENSURE(s.peek_back() == 99, "peek_back() wrong"); + + s.seek(10000); + TEST_ENSURE(s.peek_back() == 9999, "peek_back() wrong"); + s.skip(); + TEST_ENSURE(s.peek_back() == 10000, "peek_back() wrong"); + s.skip_back(); + TEST_ENSURE(s.peek_back() == 9999, "peek_back() wrong"); + + s.seek(1000000); + TEST_ENSURE(s.peek_back() == 999999, "peek_back() wrong"); + + s.skip_back(); + TEST_ENSURE(s.peek_back() == 999998, "peek_back() wrong"); + + s.seek(100000); + s.skip(); + TEST_ENSURE(s.peek_back() == 100000, "peek_back() wrong"); + + return true; +} + +bool peek_back_throw_test() { + tpie::file_stream s; + s.open(); + for (size_t i = 0; i < 1000; ++i) s.write(i); + + bool threw = false; + s.seek(0); + try { + s.peek_back(); + } catch (tpie::end_of_stream_exception &) { + threw = true; + } + TEST_ENSURE(threw, "peek_back() did throw"); + + return true; +} + bool reopen() { tpie::temp_file tf; @@ -818,5 +866,7 @@ int main(int argc, char **argv) { .test(stream_tester::user_data_test, "user_data_file") .test(peek_skip_test_1, "peek_skip_1") .test(peek_skip_test_2, "peek_skip_2") + .test(peek_back_skip_test, "peek_back_skip") + .test(peek_back_throw_test, "peek_back_throw") ; } diff --git a/tpie/compressed/stream.h b/tpie/compressed/stream.h index e0bcf8c7e..d58cbd675 100644 --- a/tpie/compressed/stream.h +++ b/tpie/compressed/stream.h @@ -1,19 +1,19 @@ // -*- mode: c++; tab-width: 4; indent-tabs-mode: t; eval: (progn (c-set-style "stroustrup") (c-set-offset 'innamespace 0)); -*- // vi:set ts=4 sts=4 sw=4 noet : // Copyright 2013, The TPIE development team -// +// // This file is part of TPIE. -// +// // TPIE is free software: you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. -// +// // TPIE is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public // License for more details. -// +// // You should have received a copy of the GNU Lesser General Public License // along with TPIE. If not, see @@ -108,14 +108,14 @@ class TPIE_EXPORT compressed_stream_base { /////////////////////////////////////////////////////////////////////////// void cache_read_writes(); - void peak_unlikely(); - + void read_unlikely(); + void read_back_unlikely(); - + void write_unlikely(const char * item); static memory_size_type memory_usage(double blockFactor=1.0) noexcept; - + public: bool is_readable() const noexcept; @@ -264,55 +264,60 @@ class TPIE_EXPORT compressed_stream_base { void close(); bool is_open() const noexcept; - + stream_size_type offset() const; /////////////////////////////////////////////////////////////////////////// - /// \brief For debugging: Describe the internal stream state in a string. + /// \internal + /// + /// \brief For debugging: Describe the internal stream state in a string. /////////////////////////////////////////////////////////////////////////// void describe(std::ostream & out); - + /////////////////////////////////////////////////////////////////////////// - /// \brief For debugging: Describe the internal stream state in a string. + /// \internal + /// + /// \brief For debugging: Describe the internal stream state in a string. /////////////////////////////////////////////////////////////////////////// std::string describe(); /////////////////////////////////////////////////////////////////////////// - /// Precondition: is_open() - /// Precondition: offset == 0 + /// \pre is_open() + /// \pre offset == 0 /////////////////////////////////////////////////////////////////////////// void seek(stream_offset_type offset, offset_type whence=beginning); - + /////////////////////////////////////////////////////////////////////////// - /// \brief Truncate to given size. + /// \brief Truncate to given size. /// - /// Precondition: compression is disabled or offset is size() or 0. - /// Blocks to take the compressor lock. + /// \pre compression is disabled or offset is size() or 0. + /// + /// \remark Blocks to take the compressor lock. /////////////////////////////////////////////////////////////////////////// void truncate(stream_size_type offset); - + /////////////////////////////////////////////////////////////////////////// - /// \brief Truncate to given stream position. + /// \brief Truncate to given stream position. /////////////////////////////////////////////////////////////////////////// void truncate(const stream_position & pos); - + /////////////////////////////////////////////////////////////////////////// - /// \brief Store the current stream position such that it may be found + /// \brief Store the current stream position such that it may be found /// later on. /// - /// The stream_position object is violated if the stream is eventually - /// truncated to before the current position. + /// \details The stream_position object is violated if the stream is + /// eventually truncated to before the current position. /// /// The stream_position objects are plain old data, so they may themselves /// be written to streams. /// - /// Blocks to take the compressor lock. + /// \remark Blocks to take the compressor lock. /////////////////////////////////////////////////////////////////////////// stream_position get_position(); - + /////////////////////////////////////////////////////////////////////////// - /// \brief Seek to a position that was previously recalled with + /// \brief Seek to a position that was previously recalled with /// \c get_position. /////////////////////////////////////////////////////////////////////////// void set_position(const stream_position & pos); @@ -321,17 +326,16 @@ class TPIE_EXPORT compressed_stream_base { stream_size_type file_size() const { return size(); } - /////////////////////////////////////////////////////////////////////////// - /// \brief Check if the next call to read() will succeed or not. + /// \brief Check if the next call to read() will succeed or not. /////////////////////////////////////////////////////////////////////////// bool can_read() { - if (m_cachedReads > 0) return true; + if (m_cachedReads > 0) return true; return offset() < size(); } /////////////////////////////////////////////////////////////////////////// - /// \brief Check if the next call to read_back() will succeed or not. + /// \brief Check if the next call to read_back() will succeed or not. /////////////////////////////////////////////////////////////////////////// bool can_read_back() { return offset() > 0; @@ -341,13 +345,13 @@ class TPIE_EXPORT compressed_stream_base { memory_size_type m_cachedReads; /** Number of cheap, unchecked writes we can do next. */ memory_size_type m_cachedWrites; - + /** Number of logical items in the stream. */ stream_size_type m_size; /** Buffer manager for this entire stream. */ - + seek_state::type m_seekState; - + /** Offset of next item to read/write, relative to beginning of stream. * Invariants: * @@ -368,7 +372,7 @@ class TPIE_EXPORT compressed_stream_base { /** Only when m_buffer.get() != 0: End of writable buffer. */ char * m_bufferEnd; - + /** Next item in buffer to read/write. */ char * m_nextItem; @@ -399,32 +403,34 @@ class TPIE_EXPORT compressed_stream_base { /// precondition (for instance by passing an invalid parameter). /////////////////////////////////////////////////////////////////////////////// template -class file_stream : public compressed_stream_base { +class file_stream : public compressed_stream_base +{ static_assert(is_stream_writable::value, "file_stream item type must be trivially copyable"); + public: typedef T item_type; - + file_stream(double blockFactor=1.0) - : compressed_stream_base(sizeof(T), blockFactor) {} - + : compressed_stream_base(sizeof(T), blockFactor) + { } + static memory_size_type memory_usage(double blockFactor=1.0) noexcept { // m_buffer is included in m_buffers memory usage return sizeof(file_stream) + compressed_stream_base::memory_usage(blockFactor); } /////////////////////////////////////////////////////////////////////////// - /// Reads next item from stream if can_read() == true. - /// - /// If can_read() == false, throws an end_of_stream_exception. + /// \brief Reads next item from stream if can_read() == true. /// - /// Blocks to take the compressor lock. + /// \exception end_of_stream_exception If can_read() == false. The stream is + /// left in the state it was in before the call to read() that threw an + /// exception. /// - /// If a stream_exception is thrown, the stream is left in the state it was - /// in before the call to read(). + /// \remark Blocks to take the compressor lock. /////////////////////////////////////////////////////////////////////////// const T & read() { if (m_cachedReads == 0) { - peak_unlikely(); + read_unlikely(); const T & res = *reinterpret_cast(m_nextItem); ++m_offset; m_nextItem += sizeof(T); @@ -437,41 +443,54 @@ class file_stream : public compressed_stream_base { m_nextItem += sizeof(T); return res; } - + /////////////////////////////////////////////////////////////////////////// - /// Peeks next item from stream if can_read() == true. + /// \brief Reads min(b-a, size()-offset()) items into the range [a, b). If + /// less than b-a items are read, throws an end_of_stream_exception. /// - /// If can_read() == false, throws an end_of_stream_exception. + /// \pre is_open(). + /////////////////////////////////////////////////////////////////////////// + template + void read(IT const a, IT const b) { + for (IT i = a; i != b; ++i) *i = read(); + } + + /////////////////////////////////////////////////////////////////////////// + /// \brief Peeks next item from stream if can_read() == true. /// - /// Blocks to take the compressor lock. + /// \exception end_of_stream_exception If can_read() == false. The stream is + /// left in the state it was in before the call to peek() that threw an + /// exception. /// - /// If a stream_exception is thrown, the stream is left in the state it was - /// in before the call to peek(). + /// \remark Blocks to take the compressor lock. /////////////////////////////////////////////////////////////////////////// const T & peek() { - if (m_cachedReads == 0) peak_unlikely(); + if (m_cachedReads == 0) read_unlikely(); return *reinterpret_cast(m_nextItem); } + /////////////////////////////////////////////////////////////////////////// + /// \brief Skips next item from stream if can_read() == true. + /// + /// \exception end_of_stream_exception If can_read() == false. The stream is + /// left in the state it was in before the call to skip() that threw an + /// exception. + /// + /// \remark Blocks to take the compressor lock. + /////////////////////////////////////////////////////////////////////////// void skip() { read(); } - void skip_back() { - read_back(); - } - /////////////////////////////////////////////////////////////////////////// - /// Precondition: is_open(). + /// \brief Reads previous item from stream if can_read_back() == true. + /// + /// \exception end_of_stream_exception If can_read_back() == false. The + /// stream is left in the state it was in before the call to read_back() + /// that threw an exception. /// - /// Reads min(b-a, size()-offset()) items into the range [a, b). - /// If less than b-a items are read, throws an end_of_stream_exception. + /// \remark Blocks to take the compressor lock. /////////////////////////////////////////////////////////////////////////// - template - void read(IT const a, IT const b) { - for (IT i = a; i != b; ++i) *i = read(); - } - const T & read_back() { if (m_seekState != seek_state::none || m_nextItem == m_bufferBegin) read_back_unlikely(); ++m_cachedReads; @@ -479,7 +498,48 @@ class file_stream : public compressed_stream_base { m_nextItem -= sizeof(T); return *reinterpret_cast(m_nextItem); } - + + /////////////////////////////////////////////////////////////////////////// + /// \brief Reads min(b-a, size()-offset()) items into the range [a, b). If + /// less than b-a items are read, throws an end_of_stream_exception. + /// + /// \pre is_open(). + /////////////////////////////////////////////////////////////////////////// + template + void read_back(IT const a, IT const b) { + for (IT i = a; i != b; ++i) *i = read_back(); + } + + /////////////////////////////////////////////////////////////////////////// + /// \brief Peeks the previous item from stream if can_read_back() == true. + /// + /// \exception end_of_stream_exception If can_read_back() == false. The + /// stream is left in the state it was in before the call to read_back() + /// that threw an exception. + /// + /// \remark Blocks to take the compressor lock. + /////////////////////////////////////////////////////////////////////////// + const T & peek_back() { + if (m_seekState != seek_state::none || m_nextItem == m_bufferBegin) read_back_unlikely(); + return *reinterpret_cast(m_nextItem - sizeof(T)); + } + + /////////////////////////////////////////////////////////////////////////// + /// \brief Skips nextprevious item from stream if can_read_back() == true. + /// + /// \exception end_of_stream_exception If can_read_back() == false. The + /// stream is left in the state it was in before the call to skip_back() + /// that threw an exception. + /// + /// \remark Blocks to take the compressor lock. + /////////////////////////////////////////////////////////////////////////// + void skip_back() { + read_back(); + } + + /////////////////////////////////////////////////////////////////////////// + /// \brief Write item to next position in stream. + /////////////////////////////////////////////////////////////////////////// void write(const T & item) { if (m_cachedWrites == 0) { write_unlikely(reinterpret_cast(&item)); @@ -493,6 +553,9 @@ class file_stream : public compressed_stream_base { return; } + /////////////////////////////////////////////////////////////////////////// + /// \brief Writes b-a items to the next immediate positions in the stream. + /////////////////////////////////////////////////////////////////////////// template void write(IT const a, IT const b) { for (IT i = a; i != b; ++i) write(*i); diff --git a/tpie/compressed/stream_base.cpp b/tpie/compressed/stream_base.cpp index ee6a856ff..48fdf7106 100644 --- a/tpie/compressed/stream_base.cpp +++ b/tpie/compressed/stream_base.cpp @@ -898,7 +898,7 @@ void compressed_stream_base::cache_read_writes() { } } -void compressed_stream_base::peak_unlikely() { +void compressed_stream_base::read_unlikely() { if (m_seekState != seek_state::none) m_p->perform_seek(); if (m_offset == m_size) throw end_of_stream_exception(); if (m_nextItem == m_bufferEnd) {