blob: dd120281859563afec3eceb2a1d563c28fa5dfb6 [file]
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#ifndef SHARE_UTILITIES_ISTREAM_HPP
#define SHARE_UTILITIES_ISTREAM_HPP
#include "memory/allocation.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"
#include "utilities/ostream.hpp"
// Input streams for reading line-oriented textual data. These streams
// treat newline '\n' very differently from all other bytes. Carriage
// return '\r' is just another bit of whitespace, although it is
// removed just before newline.
//
// Null '\0' is just a data byte, although it also terminates C
// strings; the `current_line` function adds a null after removing any
// line terminator but does not specially process any nulls embedded
// in the line.
//
// There are sizing access functions which allow lines to contain
// null, but the simpler function assumes null termination, and thus
// lines containing null will "look" shorter when viewed as C strings.
// Use the sizing access functions if you care about this.
//
// Formatting guidelines:
//
// Configuration data should be line-oriented. It should be readable
// by humans (though perhaps with difficulty). It should be easily
// processed by text editors and by widely available text processing
// tools such as grep, sed, and awk.
//
// Configuration data should not require "compilers" to generate, if
// possible. It should be editable by hand, if possible. In cases
// where binary data is strongly required, pick a binary format
// already native to Hotspot, such as classfile, jar, or jmod.
//
// Each line should be separately parseable; the parsing can be ad
// hoc. For constructs inherently larger than single lines (such as
// complex method configuration information), try to use a structuring
// principle that allows "leaf" data to be line-oriented, and delimits
// that data with markup lines of some sort. Try to pick a
// line-friendly version of a standard format like XML or Markdown.
// JSON is somewhat problematic because there is no line-friendly leaf
// syntax: everything at the leaves must be a quoted string in JSON.
//
// Use simple parsing via scanf-like formats for simple applications.
// But, keep in mind that these formats may lose data when applied to
// unusual strings, such as class names that contain spaces, or method
// names that contain punctuation. For more robust transmission of
// potentially unusual names, consider wrapping them in XML-flavored
// lines like <tag attr='pay load'/>.
//
// Note: Input streams are never MT-safe.
class inputStream : public CHeapObjBase {
public:
class Input;
private:
NONCOPYABLE(inputStream);
static constexpr size_t SMALL_SIZE = 240 DEBUG_ONLY(*0 + 10);
static constexpr size_t BIG_SIZE = 2048 DEBUG_ONLY(*0 + 20);
protected:
// Values for _input_state, to distinguish some phases of history:
// Do we need to read more input (NTR)? Did we see EOF already?
// Was there an error getting input or allocating buffer space?
enum class IState : int { NTR_STATE, EOF_STATE, ERR_STATE };
// Named offset for _next relative to _content_end, of phantom '\n'.
static const int NEXT_PHANTOM = 1;
Input* _input; // where the input comes from or else nullptr
IState _input_state; // one of {NTR,EOF,ERR}_STATE
char* _buffer; // scratch buffer holding at least the current line
size_t _buffer_size; // allocated size of buffer
size_t _content_end; // offset to end of valid contents of buffer
size_t _beg; // offset in buffer to start of current line
size_t _end; // offset to end of known current line (else content_end)
size_t _next; // offset to known start of next line (else =end)
size_t _line_count; // increasing non-resettable count of lines read
char _small_buffer[SMALL_SIZE]; // stack-allocated buffer for holding lines;
// will switch to C_HEAP allocation when necessary.
bool has_c_heap_buffer() {
return _buffer != &_small_buffer[0];
}
// Buffer states
//
// The current line (less any line ending) is always [beg..end).
// It is always the case that 0 <= beg <= end <= con_end <= buffer_size.
// When there is a current line buffered, end < next <= 1+con_end.
// In that case, the value of next is end + max(1, strlen(lend)),
// where lend is "\n", "\r\n", or (for a last partial line) "".
// But if next == end, we need to read more input, or observe an EOF.
//
// beg ==end ==next == con_end => nothing buffered, we need to read
// beg <=end < next <= con_end => have current line, with terminator
// beg < end < next ==1+con_end => have partial current line (saw EOF)
// beg < end ==next == con_end => partial line, we need to read
// beg ==end < next ==1+con_end => definitely done; no more I/O
//
// These states are in three mutually exclusive groups:
// need_to_read() <= nothing or partial line in buffer
// have_current_line() <= beg/end point to valid line (partial only if EOF)
// definitely_done() <= consumed all lines && (hit EOF || hit error)
// These states are internal; the user can only look at next/done/error.
//
// Relative to these states, everything already read from the input
// before the first byte of the current line is logically present
// (but not accessible) before _beg, while everything not yet read
// from the input is after _content_end. The difference between
// these two pointers is constant, except when characters change
// from being in the current line to being (logically) before it,
// when next is called.
bool is_sane() const {
assert(_buffer != nullptr, "");
assert(_content_end <= _buffer_size, "");
assert(_beg <= _end && _end <= _content_end, "");
assert(_end <= _next && _next <= _content_end + NEXT_PHANTOM, "");
assert(_buffer_size == 0 || _next <= _buffer_size, "");
return true;
}
bool need_to_read() const {
assert(is_sane(), "");
return _next == _end;
}
bool have_current_line() const {
assert(is_sane(), "");
// _beg < _content_end because there is an \0 (was \n) at _end,
// or else it is a non-empty partial line and the \0 is at
// _content_end. In either case, if _end == _next we are
// still searching for more input.
return (_beg < _content_end && _end < _next);
}
bool definitely_done() const {
assert(is_sane(), "");
// If _beg < _content_end we still have a line of some sort.
// Otherwise, if _next > _content_end, we have seen EOF or error.
return (_beg == _content_end && _next > _content_end);
}
// Reset indexes within the buffer to point to no content.
void clear_buffer();
// Reset indexes within the buffer to point to the given content.
// This is where we scan for newlines as well.
void set_buffer_content(size_t content_start, size_t content_end);
// Try to make the buffer bigger. This may be necessary in order to
// buffer a very long line. Returns false if there was an
// allocation failure.
//
// On allocation failure, just make do with whatever buffer there
// was to start with; the caller must check for this condition and
// avoid buffering more data in the non-expanded buffer. However,
// the buffer will always be non-null, so at least one line can be
// buffered, if it is of normal size.
bool expand_buffer(size_t new_length);
// Make sure there is at least one line in the buffer, and set
// _beg/_end to indicate where it is. Any content before _beg can
// be overwritten to make more room in the buffer. If there is no
// more input, set the state up to indicate we are done.
bool fill_buffer();
// Find some room in the buffer so we can call read on it.
// This might call expand_buffer but will try not to.
// The assumption is that read already buffers slow I/O calls.
// The purpose for the small buffer managed here is to store whole lines,
// and perhaps edit them in-place.
void prepare_to_fill_buffer(size_t& fill_offset, size_t& fill_length);
// Quick check for an initially incomplete buffer...
void preload() const {
if (need_to_read()) {
const_cast<inputStream*>(this)->fill_buffer();
}
}
// How much content is buffered (if any) after the current line?
size_t buffered_content_length(bool include_current) const {
return (include_current ? _content_end - _beg :
_content_end >= _next ? _content_end - _next : 0);
}
// Returns a pointer and count to characters buffered after the
// current line, but not yet read from my input source. Only useful
// if you are trying to stack input streams on top of each other
// somehow. You can also ask the input source if it thinks it has
// more bytes.
const char* next_content(size_t& next_content_length) const;
public:
// Create an empty input stream.
// Call push_back_input or set_input to configure.
inputStream() :
_input(nullptr),
_input_state(IState::NTR_STATE),
_buffer(&_small_buffer[0]),
_buffer_size(sizeof(_small_buffer)),
_content_end(0),
_beg(0),
_end(0),
_next(0),
_line_count(0) {}
// Take input from the given source. Buffer only a modest amount.
inputStream(Input* input)
: inputStream()
{
set_input(input);
}
virtual ~inputStream();
// Discards any previous input and sets the given input source.
void set_input(Input* input);
// Returns a pointer to a null terminated mutable copy of the current line.
// Note that embedded nulls may make the line appear shorter than it really is.
// This may trigger input activity if there is not enough data buffered.
// If there are no more lines, return an empty line, statically allocated.
char* current_line() const {
preload();
if (definitely_done())
return (char*)"";
return &_buffer[_beg];
}
// Return the size of the current line, exclusive of any line terminator.
// If no lines have been read yet, or there are none remaining, return zero.
size_t current_line_length() const {
preload();
return _end - _beg;
}
// Reports my current input source, if any, else a null pointer.
Input* input() const { return _input; }
// Discards the current line, gets ready to report the next line.
// Returns true if there is one, which is always the opposite of done().
// Fetches input if necessary.
bool next();
// Reports if there are no more lines. Fetches input if necessary.
bool done() const {
preload();
return definitely_done();
}
// Discard pending input and do not read any more.
// Takes no action if already done, whether in an error state or not.
void set_done();
// Reports if this stream has had an error was reported on it.
bool error() const {
return _input_state == IState::ERR_STATE;
}
// Set this stream done with an error, if the argument is true.
// If it is false but there is an error condition, clear the error.
// Otherwise do nothing.
void set_error(bool error_condition = true);
// lineno is the 1-based ordinal of the current line; it starts at one
size_t lineno() const { preload(); return _line_count; }
// Copy the current line to the given output stream.
void print_on(outputStream* out);
// Copy the current line to the given output stream, and also call cr().
void print_cr_on(outputStream* out) {
print_on(out); out->cr();
}
#ifdef ASSERT
void dump(const char* what = nullptr);
static int coverage_mode(int mode, int& cases, int& total, int& zeroes);
#else
void dump(const char* what = nullptr) { }
#endif
// Block-oriented input, which treats all bytes equally.
class Input : public CHeapObjBase {
public:
// Read some characters from an external source into the line buffer.
// If there are no more, return zero, otherwise return non-zero.
// It must be OK to call read even after it returns zero.
virtual size_t read(char* buf, size_t size) = 0;
// Example: read(b,s) { return fread(b, 1, s, _my_fp); }
// Example: read(b,s) { return 0; } // never more than the initial buffer
};
};
// for reading lines from files
class FileInput : public inputStream::Input {
NONCOPYABLE(FileInput);
protected:
fileStream& _fs;
fileStream _private_fs;
// it does not seem likely there are such file streams around
FileInput(fileStream& fs)
: _fs(fs)
{ }
public:
// just forward all the constructor arguments to the wrapped line-input class
template<typename... Arg>
FileInput(Arg... arg)
: _fs(_private_fs), _private_fs(arg...)
{ }
FileInput(const char* file_name)
: FileInput(file_name, "rt")
{ }
bool is_open() const { return _fs.is_open(); }
protected:
size_t read(char* buf, size_t size) override {
return _fs.read(buf, size);
}
};
class MemoryInput : public inputStream::Input {
const void* _base;
const size_t _limit;
size_t _offset;
public:
MemoryInput(const void* base, size_t size,
size_t offset = 0)
: _base(base), _limit(size), _offset(offset) {}
MemoryInput(const char* start)
: MemoryInput(start, 0, strlen(start))
{ }
protected:
size_t read(char* buf, size_t size) override {
size_t nr = size;
if (nr > _limit - _offset) {
nr = _limit - _offset;
}
if (nr > 0) {
::memcpy(buf, (char*)_base + _offset, nr);
_offset += nr;
}
return nr;
}
};
#endif // SHARE_UTILITIES_ISTREAM_HPP