ecto
streambuf.hpp
Go to the documentation of this file.
1 // Willow Garage BSD License not applicable
2 #pragma once
3 // original source from http://cci.lbl.gov/cctbx_sources/boost_adaptbx/python_streambuf.h
4 // original license
5 //*** Copyright Notice ***
6 //
7 //cctbx Copyright (c) 2006, The Regents of the University of
8 //California, through Lawrence Berkeley National Laboratory (subject to
9 //receipt of any required approvals from the U.S. Dept. of Energy). All
10 //rights reserved.
11 //
12 //If you have questions about your rights to use or distribute this
13 //software, please contact Berkeley Lab's Technology Transfer Department
14 //at TTD@lbl.gov referring to "cctbx (LBNL Ref CR-1726)"
15 //
16 //This software package includes code written by others which may be
17 //governed by separate license agreements. Please refer to the associated
18 //licenses for further details.
19 //
20 //NOTICE. This software was developed under funding from the U.S.
21 //Department of Energy. As such, the U.S. Government has been granted for
22 //itself and others acting on its behalf a paid-up, nonexclusive,
23 //irrevocable, worldwide license in the Software to reproduce, prepare
24 //derivative works, and perform publicly and display publicly. Beginning
25 //five (5) years after the date permission to assert copyright is obtained
26 //from the U.S. Department of Energy, and subject to any subsequent five
27 //(5) year renewals, the U.S. Government is granted for itself and others
28 //acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide
29 //license in the Software to reproduce, prepare derivative works,
30 //distribute copies to the public, perform publicly and display publicly,
31 //and to permit others to do so.
32 //
33 //*** License agreement ***
34 //
35 //cctbx Copyright (c) 2006, The Regents of the University of
36 //California, through Lawrence Berkeley National Laboratory (subject to
37 //receipt of any required approvals from the U.S. Dept. of Energy). All
38 //rights reserved.
39 //
40 //Redistribution and use in source and binary forms, with or without
41 //modification, are permitted provided that the following conditions are met:
42 //
43 //(1) Redistributions of source code must retain the above copyright
44 //notice, this list of conditions and the following disclaimer.
45 //
46 //(2) Redistributions in binary form must reproduce the above copyright
47 //notice, this list of conditions and the following disclaimer in the
48 //documentation and/or other materials provided with the distribution.
49 //
50 //(3) Neither the name of the University of California, Lawrence Berkeley
51 //National Laboratory, U.S. Dept. of Energy nor the names of its
52 //contributors may be used to endorse or promote products derived from
53 //this software without specific prior written permission.
54 //
55 //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
56 //IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
57 //TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
58 //PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
59 //OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
60 //EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
61 //PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
62 //PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
63 //LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
64 //NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
65 //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
66 //
67 //You are under no obligation whatsoever to provide any bug fixes,
68 //patches, or upgrades to the features, functionality or performance of
69 //the source code ("Enhancements") to anyone; however, if you choose to
70 //make your Enhancements available either publicly, or directly to
71 //Lawrence Berkeley National Laboratory, without imposing a separate
72 //written license agreement for such Enhancements, then you hereby grant
73 //the following license: a non-exclusive, royalty-free perpetual license
74 //to install, use, modify, prepare derivative works, incorporate into
75 //other computer software, distribute, and sublicense such enhancements or
76 //derivative works thereof, in binary and source code form.
77 #include <boost/python/object.hpp>
78 #include <boost/python/str.hpp>
79 #include <boost/python/extract.hpp>
80 
81 #include <boost/optional.hpp>
82 #include <boost/utility/typed_in_place_factory.hpp>
83 
84 #include <streambuf>
85 #include <iostream>
86 
87 namespace ecto { namespace py {
88 
89 namespace bp = boost::python;
90 
92 
165 inline
166 std::string
168  const char* file,
169  long line)
170 {
171  std::ostringstream o;
172  o << file << "(" << line << ")";
173  return o.str();
174 }
175 
176 #define TBXX_ASSERT(condition) \
177  if (!(condition)) { \
178  throw std::runtime_error( \
179  file_and_line_as_string( \
180  __FILE__, __LINE__) \
181  + ": ASSERT(" #condition ") failure."); \
182  }
183 
184 #define TBXX_UNREACHABLE_ERROR() \
185  std::runtime_error( \
186  "Control flow passes through branch that should be unreachable: " \
187  + file_and_line_as_string(__FILE__, __LINE__))
188 
189 class streambuf : public std::basic_streambuf<char>
190 {
191  private:
192  typedef std::basic_streambuf<char> base_t;
193 
194  public:
195  /* The syntax
196  using base_t::char_type;
197  would be nicer but Visual Studio C++ 8 chokes on it
198  */
199  typedef base_t::char_type char_type;
200  typedef base_t::int_type int_type;
201  typedef base_t::pos_type pos_type;
202  typedef base_t::off_type off_type;
203  typedef base_t::traits_type traits_type;
204 
205  // work around Visual C++ 7.1 problem
206  inline static int
207  traits_type_eof() { return traits_type::eof(); }
208 
210 
213  static std::size_t default_buffer_size;
214 
216 
219  bp::object& python_file_obj,
220  std::size_t buffer_size_=0)
221  :
222  py_read (getattr(python_file_obj, "read", bp::object())),
223  py_write(getattr(python_file_obj, "write", bp::object())),
224  py_seek (getattr(python_file_obj, "seek", bp::object())),
225  py_tell (getattr(python_file_obj, "tell", bp::object())),
226  buffer_size(buffer_size_ != 0 ? buffer_size_ : default_buffer_size),
227  write_buffer(0),
230  farthest_pptr(0),
231  file_obj(python_file_obj)
232  {
233  TBXX_ASSERT(buffer_size != 0);
234  /* Some Python file objects (e.g. sys.stdout and sys.stdin)
235  have non-functional seek and tell. If so, assign None to
236  py_tell and py_seek.
237  */
238  if (py_tell != bp::object()) {
239  try {
240  py_tell();
241  }
242  catch (bp::error_already_set&) {
243  py_tell = bp::object();
244  py_seek = bp::object();
245  /* Boost.Python does not do any Python exception handling whatsoever
246  So we need to catch it by hand like so.
247  */
248  PyErr_Clear();
249  }
250  }
251 
252  if (py_write != bp::object()) {
253  // C-like string to make debugging easier
254  write_buffer = new char[buffer_size + 1];
255  write_buffer[buffer_size] = '\0';
256  setp(write_buffer, write_buffer + buffer_size); // 27.5.2.4.5 (5)
257  farthest_pptr = pptr();
258  }
259  else {
260  // The first attempt at output will result in a call to overflow
261  setp(0, 0);
262  }
263 
264  if (py_tell != bp::object()) {
265  off_type py_pos = bp::extract<off_type>(py_tell());
268  }
269  }
270 
272  virtual ~streambuf() {
273  if (write_buffer) delete[] write_buffer;
274  }
275 
277 
280  virtual std::streamsize showmanyc() {
281  int_type const failure = traits_type::eof();
282  int_type status = underflow();
283  if (status == failure) return -1;
284  return egptr() - gptr();
285  }
286 
288  virtual int_type underflow() {
289  int_type const failure = traits_type::eof();
290  if (py_read == bp::object()) {
291  throw std::invalid_argument(
292  "That Python file object has no 'read' attribute");
293  }
295  char *read_buffer_data;
296  bp::ssize_t py_n_read;
297  if (PyString_AsStringAndSize(read_buffer.ptr(),
298  &read_buffer_data, &py_n_read) == -1) {
299  setg(0, 0, 0);
300  throw std::invalid_argument(
301  "The method 'read' of the Python file object "
302  "did not return a string.");
303  }
304  off_type n_read = (off_type)py_n_read;
306  setg(read_buffer_data, read_buffer_data, read_buffer_data + n_read);
307  // ^^^27.5.2.3.1 (4)
308  if (n_read == 0) return failure;
309  return traits_type::to_int_type(read_buffer_data[0]);
310  }
311 
313  virtual int_type overflow(int_type c=traits_type_eof()) {
314  if (py_write == bp::object()) {
315  throw std::invalid_argument(
316  "That Python file object has no 'write' attribute");
317  }
318  farthest_pptr = std::max(farthest_pptr, pptr());
319  off_type n_written = (off_type)(farthest_pptr - pbase());
320  bp::str chunk(pbase(), farthest_pptr);
321  py_write(chunk);
322  if (!traits_type::eq_int_type(c, traits_type::eof())) {
323  py_write(traits_type::to_char_type(c));
324  n_written++;
325  }
326  if (n_written) {
328  setp(pbase(), epptr());
329  // ^^^ 27.5.2.4.5 (5)
330  farthest_pptr = pptr();
331  }
332  return traits_type::eq_int_type(
333  c, traits_type::eof()) ? traits_type::not_eof(c) : c;
334  }
335 
337 
343  virtual int sync() {
344  int result = 0;
345  farthest_pptr = std::max(farthest_pptr, pptr());
346  if (farthest_pptr && farthest_pptr > pbase()) {
347  off_type delta = pptr() - farthest_pptr;
348  int_type status = overflow();
349  if (traits_type::eq_int_type(status, traits_type::eof())) result = -1;
350  if (py_seek != bp::object()) py_seek(delta, 1);
351  }
352  else if (gptr() && gptr() < egptr()) {
353  if (py_seek != bp::object()) py_seek(gptr() - egptr(), 1);
354  }
355  return result;
356  }
357 
359 
365  virtual
366  pos_type seekoff(off_type off, std::ios_base::seekdir way,
367  std::ios_base::openmode which= std::ios_base::in
368  | std::ios_base::out)
369  {
370  /* In practice, "which" is either std::ios_base::in or out
371  since we end up here because either seekp or seekg was called
372  on the stream using this buffer. That simplifies the code
373  in a few places.
374  */
375  int const failure = off_type(-1);
376 
377  if (py_seek == bp::object()) {
378  throw std::invalid_argument(
379  "That Python file object has no 'seek' attribute");
380  }
381 
382  // we need the read buffer to contain something!
383  if (which == std::ios_base::in && !gptr()) {
384  if (traits_type::eq_int_type(underflow(), traits_type::eof())) {
385  return failure;
386  }
387  }
388 
389  // compute the whence parameter for Python seek
390  int whence;
391  switch (way) {
392  case std::ios_base::beg:
393  whence = 0;
394  break;
395  case std::ios_base::cur:
396  whence = 1;
397  break;
398  case std::ios_base::end:
399  whence = 2;
400  break;
401  default:
402  return failure;
403  }
404 
405  // Let's have a go
406  boost::optional<off_type> result = seekoff_without_calling_python(
407  off, way, which);
408  if (!result) {
409  // we need to call Python
410  if (which == std::ios_base::out) overflow();
411  if (way == std::ios_base::cur) {
412  if (which == std::ios_base::in) off -= egptr() - gptr();
413  else if (which == std::ios_base::out) off += pptr() - pbase();
414  }
415  py_seek(off, whence);
416  result = off_type(bp::extract<off_type>(py_tell()));
417  if (which == std::ios_base::in) underflow();
418  }
419  return *result;
420  }
421 
423  virtual
424  pos_type seekpos(pos_type sp,
425  std::ios_base::openmode which= std::ios_base::in
426  | std::ios_base::out)
427  {
428  return streambuf::seekoff(sp, std::ios_base::beg, which);
429  }
430 
431 
432  private:
434 
435  std::size_t buffer_size;
436 
437  /* This is actually a Python string and the actual read buffer is
438  its internal data, i.e. an array of characters. We use a Boost.Python
439  object so as to hold on it: as a result, the actual buffer can't
440  go away.
441  */
442  bp::object read_buffer;
443 
444  /* A mere array of char's allocated on the heap at construction time and
445  de-allocated only at destruction time.
446  */
448 
451 
452  // the farthest place the buffer has been written into
454 
455 
456  boost::optional<off_type> seekoff_without_calling_python(
457  off_type off,
458  std::ios_base::seekdir way,
459  std::ios_base::openmode which)
460  {
461  boost::optional<off_type> const failure;
462 
463  // Buffer range and current position
464  off_type buf_begin, buf_end, buf_cur, upper_bound;
465  off_type pos_of_buffer_end_in_py_file;
466  if (which == std::ios_base::in) {
467  pos_of_buffer_end_in_py_file = pos_of_read_buffer_end_in_py_file;
468  buf_begin = reinterpret_cast<std::streamsize>(eback());
469  buf_cur = reinterpret_cast<std::streamsize>(gptr());
470  buf_end = reinterpret_cast<std::streamsize>(egptr());
471  upper_bound = buf_end;
472  }
473  else if (which == std::ios_base::out) {
474  pos_of_buffer_end_in_py_file = pos_of_write_buffer_end_in_py_file;
475  buf_begin = reinterpret_cast<std::streamsize>(pbase());
476  buf_cur = reinterpret_cast<std::streamsize>(pptr());
477  buf_end = reinterpret_cast<std::streamsize>(epptr());
478  farthest_pptr = std::max(farthest_pptr, pptr());
479  upper_bound = reinterpret_cast<std::streamsize>(farthest_pptr) + 1;
480  }
481  else {
482  throw TBXX_UNREACHABLE_ERROR();
483  }
484 
485  // Sought position in "buffer coordinate"
486  off_type buf_sought;
487  if (way == std::ios_base::cur) {
488  buf_sought = buf_cur + off;
489  }
490  else if (way == std::ios_base::beg) {
491  buf_sought = buf_end + (off - pos_of_buffer_end_in_py_file);
492  }
493  else if (way == std::ios_base::end) {
494  return failure;
495  }
496  else {
497  throw TBXX_UNREACHABLE_ERROR();
498  }
499 
500  // if the sought position is not in the buffer, give up
501  if (buf_sought < buf_begin || buf_sought >= upper_bound) return failure;
502 
503  // we are in wonderland
504  if (which == std::ios_base::in) gbump(buf_sought - buf_cur);
505  else if (which == std::ios_base::out) pbump(buf_sought - buf_cur);
506  return pos_of_buffer_end_in_py_file + (buf_sought - buf_end);
507  }
508 
509  public:
510 
511  class istream : public std::istream
512  {
513  public:
514  istream(streambuf& buf) : std::istream(&buf)
515  {
516  exceptions(std::ios_base::badbit);
517  }
518 
519  ~istream() { if (this->good()) this->sync(); }
520  };
521 
522  class ostream : public std::ostream
523  {
524  public:
525  ostream(streambuf& buf) : std::ostream(&buf)
526  {
527  exceptions(std::ios_base::badbit);
528  }
529 
530  ~ostream() { if (this->good()) this->flush(); }
531  };
532  bp::object file_obj; //original handle
533 };
534 
535 std::size_t streambuf::default_buffer_size = 1024;
536 
538 {
540 
542  bp::object& python_file_obj,
543  std::size_t buffer_size=0)
544  :
545  python_streambuf(python_file_obj, buffer_size)
546  {}
547 
548  bp::object get_original_file() const {return python_streambuf.file_obj;}
549 };
550 
552 {
554  bp::object& python_file_obj,
555  std::size_t buffer_size=0)
556  :
557  streambuf_capsule(python_file_obj, buffer_size),
558  streambuf::ostream(python_streambuf)
559  {}
560 
562  {
563  try {
564  if (this->good()) this->flush();
565  }
566  catch (bp::error_already_set&) {
567  PyErr_Clear();
568  // fixme this should not throw in the detructor
569  // throw std::runtime_error(
570  std::cerr << (
571  "Problem closing python ostream.\n"
572  " Known limitation: the error is unrecoverable. Sorry.\n"
573  " Suggestion for programmer: add ostream.flush() before"
574  " returning.") << std::endl;
575  }
576  }
577 };
578 
580 {
582  bp::object& python_file_obj,
583  std::size_t buffer_size=0)
584  :
585  streambuf_capsule(python_file_obj, buffer_size),
586  streambuf::istream(python_streambuf)
587  {}
588 };
589 
590 }} // boost_adaptbx::python
591 
bp::object py_read
Definition: streambuf.hpp:433
base_t::char_type char_type
Definition: streambuf.hpp:199
~ostream()
Definition: streambuf.hpp:530
istream(streambuf &buf)
Definition: streambuf.hpp:514
virtual std::streamsize showmanyc()
C.f. C++ standard section 27.5.2.4.3.
Definition: streambuf.hpp:280
base_t::off_type off_type
Definition: streambuf.hpp:202
streambuf python_streambuf
Definition: streambuf.hpp:539
Definition: streambuf.hpp:189
bp::object py_tell
Definition: streambuf.hpp:433
bp::object get_original_file() const
Definition: streambuf.hpp:548
bp::object read_buffer
Definition: streambuf.hpp:442
#define TBXX_UNREACHABLE_ERROR()
Definition: streambuf.hpp:184
bp::object py_write
Definition: streambuf.hpp:433
bp::object file_obj
Definition: streambuf.hpp:532
streambuf_capsule(bp::object &python_file_obj, std::size_t buffer_size=0)
Definition: streambuf.hpp:541
std::size_t buffer_size
Definition: streambuf.hpp:435
virtual int_type underflow()
C.f. C++ standard section 27.5.2.4.3.
Definition: streambuf.hpp:288
std::basic_streambuf< char > base_t
Definition: streambuf.hpp:192
base_t::pos_type pos_type
Definition: streambuf.hpp:201
base_t::traits_type traits_type
Definition: streambuf.hpp:203
Definition: parameters.hpp:11
off_type pos_of_read_buffer_end_in_py_file
Definition: streambuf.hpp:449
virtual pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which=std::ios_base::in|std::ios_base::out)
C.f. C++ standard section 27.5.2.4.2.
Definition: streambuf.hpp:366
ostream(bp::object &python_file_obj, std::size_t buffer_size=0)
Definition: streambuf.hpp:553
Definition: streambuf.hpp:537
Definition: streambuf.hpp:511
streambuf(bp::object &python_file_obj, std::size_t buffer_size_=0)
Construct from a Python file object.
Definition: streambuf.hpp:218
~istream()
Definition: streambuf.hpp:519
Definition: streambuf.hpp:551
base_t::int_type int_type
Definition: streambuf.hpp:200
char * write_buffer
Definition: streambuf.hpp:447
~ostream()
Definition: streambuf.hpp:561
off_type pos_of_write_buffer_end_in_py_file
Definition: streambuf.hpp:449
static std::size_t default_buffer_size
The default size of the read and write buffer.
Definition: streambuf.hpp:213
ostream(streambuf &buf)
Definition: streambuf.hpp:525
virtual int_type overflow(int_type c=traits_type_eof())
C.f. C++ standard section 27.5.2.4.5.
Definition: streambuf.hpp:313
virtual int sync()
Update the python file to reflect the state of this stream buffer.
Definition: streambuf.hpp:343
Definition: streambuf.hpp:522
char * farthest_pptr
Definition: streambuf.hpp:453
boost::optional< off_type > seekoff_without_calling_python(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which)
Definition: streambuf.hpp:456
Definition: streambuf.hpp:579
istream(bp::object &python_file_obj, std::size_t buffer_size=0)
Definition: streambuf.hpp:581
bp::object py_seek
Definition: streambuf.hpp:433
std::string file_and_line_as_string(const char *file, long line)
A stream buffer getting data from and putting data into a Python file object.
Definition: streambuf.hpp:167
static int traits_type_eof()
Definition: streambuf.hpp:207
Definition: std_map_indexing_suite.hpp:20
#define TBXX_ASSERT(condition)
Definition: streambuf.hpp:176
virtual ~streambuf()
Mundane destructor freeing the allocated resources.
Definition: streambuf.hpp:272
virtual pos_type seekpos(pos_type sp, std::ios_base::openmode which=std::ios_base::in|std::ios_base::out)
C.f. C++ standard section 27.5.2.4.2.
Definition: streambuf.hpp:424