Emeric Vigier | 2f62582 | 2012-08-06 11:09:52 -0400 | [diff] [blame] | 1 | // Copyright (C) 1999-2005 Open Source Telecom Corporation. |
| 2 | // Copyright (C) 2006-2010 David Sugar, Tycho Softworks. |
| 3 | // |
| 4 | // This program is free software; you can redistribute it and/or modify |
| 5 | // it under the terms of the GNU General Public License as published by |
| 6 | // the Free Software Foundation; either version 2 of the License, or |
| 7 | // (at your option) any later version. |
| 8 | // |
| 9 | // This program is distributed in the hope that it will be useful, |
| 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | // GNU General Public License for more details. |
| 13 | // |
| 14 | // You should have received a copy of the GNU General Public License |
| 15 | // along with this program; if not, write to the Free Software |
| 16 | // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 17 | // |
| 18 | // As a special exception, you may use this file as part of a free software |
| 19 | // library without restriction. Specifically, if other files instantiate |
| 20 | // templates or use macros or inline functions from this file, or you compile |
| 21 | // this file and link it with other files to produce an executable, this |
| 22 | // file does not by itself cause the resulting executable to be covered by |
| 23 | // the GNU General Public License. This exception does not however |
| 24 | // invalidate any other reasons why the executable file might be covered by |
| 25 | // the GNU General Public License. |
| 26 | // |
| 27 | // This exception applies only to the code released under the name GNU |
| 28 | // Common C++. If you copy code from other releases into a copy of GNU |
| 29 | // Common C++, as the General Public License permits, the exception does |
| 30 | // not apply to the code that you add in this way. To avoid misleading |
| 31 | // anyone as to the status of such modified files, you must delete |
| 32 | // this exception notice from them. |
| 33 | // |
| 34 | // If you write modifications of your own for GNU Common C++, it is your choice |
| 35 | // whether to permit this exception to apply to your modifications. |
| 36 | // If you do not wish that, delete this exception notice. |
| 37 | // |
| 38 | |
| 39 | #include <cc++/config.h> |
| 40 | #include <cc++/string.h> |
| 41 | #include <cc++/exception.h> |
| 42 | #include <cstdlib> |
| 43 | |
| 44 | #if !defined(_MSC_VER) || _MSC_VER >= 1300 |
| 45 | |
| 46 | #include <cc++/export.h> |
| 47 | #include <cc++/persist.h> |
| 48 | #ifndef HAVE_EXCEPTION |
| 49 | #include "assert.h" |
| 50 | #endif |
| 51 | |
| 52 | #ifdef CCXX_NAMESPACES |
| 53 | namespace ost { |
| 54 | using namespace std; |
| 55 | #endif |
| 56 | |
| 57 | #ifndef NO_COMPRESSION |
| 58 | const uint32 MAX_BUFFER = 16384; |
| 59 | #endif |
| 60 | |
| 61 | /** |
| 62 | * NullObject is a const uint32 which is the ID streamed to disk |
| 63 | * if an attempt to stream a NULL Persistence::BaseObject or |
| 64 | * Derivative is made... |
| 65 | */ |
| 66 | |
| 67 | const uint32 NullObject = 0xffffffff; |
| 68 | |
| 69 | Engine::Engine(std::iostream& stream, EngineMode mode, bool compress) THROWS (PersistException) : |
| 70 | myUnderlyingStream(stream), myOperationalMode(mode), use_compression(compress) |
| 71 | { |
| 72 | // Nothing else to initialise for now |
| 73 | #ifndef NO_COMPRESSION |
| 74 | if ( use_compression ) { |
| 75 | myZStream.zalloc = ( alloc_func ) NULL; |
| 76 | myZStream.zfree = ( free_func ) NULL; |
| 77 | myZStream.opaque = ( voidpf ) NULL; |
| 78 | myCompressedDataBuffer = new uint8[MAX_BUFFER]; |
| 79 | myUncompressedDataBuffer = new uint8[MAX_BUFFER]; |
| 80 | myLastUncompressedDataRead = myUncompressedDataBuffer; |
| 81 | if ( myOperationalMode == modeRead ) { |
| 82 | myZStream.next_in = myCompressedDataBuffer; |
| 83 | myZStream.next_out = myUncompressedDataBuffer; |
| 84 | myZStream.avail_in = 0; |
| 85 | myZStream.avail_out = MAX_BUFFER; |
| 86 | int err = inflateInit ( &myZStream ); |
| 87 | if ( err != Z_OK ) { |
| 88 | THROW ( PersistException ( String ( "zLib didn't initialise for inflating." ) ) ); |
| 89 | } |
| 90 | } |
| 91 | else { |
| 92 | myZStream.next_in = myUncompressedDataBuffer; |
| 93 | myZStream.next_out = myCompressedDataBuffer; |
| 94 | myZStream.avail_in = 0; |
| 95 | myZStream.avail_out = MAX_BUFFER; |
| 96 | int err = deflateInit ( &myZStream, 9 ); // TODO: tweak compression level |
| 97 | if ( err != Z_OK ) { |
| 98 | THROW ( PersistException ( String ( "zLib didn't initialise for deflating." ) ) ); |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | #endif |
| 103 | } |
| 104 | |
| 105 | bool Engine::more() |
| 106 | { |
| 107 | #ifndef NO_COMPRESSION |
| 108 | if ( use_compression && myOperationalMode == modeRead ) |
| 109 | return ( myLastUncompressedDataRead < myZStream.next_out ); |
| 110 | #endif |
| 111 | return false; |
| 112 | } |
| 113 | |
| 114 | void Engine::sync() |
| 115 | { |
| 116 | // Flush compression buffers etc here. |
| 117 | #ifndef NO_COMPRESSION |
| 118 | if ( use_compression ) { |
| 119 | if ( myOperationalMode == modeRead ) { |
| 120 | inflateEnd ( &myZStream ); |
| 121 | } |
| 122 | else { |
| 123 | int zret = Z_OK; |
| 124 | while ( myZStream.avail_in > 0 || zret == Z_OK ) { |
| 125 | zret = deflate ( &myZStream, Z_FINISH ); |
| 126 | if ( myZStream.avail_out >= 0 ) { |
| 127 | myUnderlyingStream.write ( ( char* ) myCompressedDataBuffer, MAX_BUFFER - myZStream.avail_out ); |
| 128 | myZStream.next_out = myCompressedDataBuffer; |
| 129 | myZStream.avail_out = MAX_BUFFER; |
| 130 | } |
| 131 | } |
| 132 | deflateEnd ( &myZStream ); |
| 133 | } |
| 134 | } |
| 135 | #endif |
| 136 | } |
| 137 | |
| 138 | Engine::~Engine() |
| 139 | { |
| 140 | if ( myUnderlyingStream.good() ) |
| 141 | sync(); |
| 142 | |
| 143 | #ifndef NO_COMPRESSION |
| 144 | if ( use_compression ) { |
| 145 | delete [] myCompressedDataBuffer; |
| 146 | delete [] myUncompressedDataBuffer; |
| 147 | } |
| 148 | #endif |
| 149 | } |
| 150 | |
| 151 | void Engine::writeBinary(const uint8* data, const uint32 size) THROWS (PersistException) |
| 152 | { |
| 153 | if ( myOperationalMode != modeWrite ) |
| 154 | THROW (PersistException( "Cannot write to an input Engine" )); |
| 155 | #ifdef NO_COMPRESSION |
| 156 | myUnderlyingStream.write((const char *)data,size); |
| 157 | #else |
| 158 | if (use_compression) { |
| 159 | // Compress the data here and doit :) |
| 160 | uint32 written = 0; |
| 161 | while ( written < size ) { |
| 162 | // transfer as much information as we can into the input buffer. |
| 163 | if ( myZStream.avail_in < MAX_BUFFER ) { |
| 164 | uint32 toAdd = size - written; |
| 165 | if ( toAdd > ( MAX_BUFFER - myZStream.avail_in ) ) |
| 166 | toAdd = ( MAX_BUFFER - myZStream.avail_in ); |
| 167 | memcpy ( myZStream.next_in + myZStream.avail_in, data + written, toAdd ); |
| 168 | written += toAdd; |
| 169 | myZStream.avail_in += toAdd; |
| 170 | } |
| 171 | if ( myZStream.avail_in < MAX_BUFFER ) |
| 172 | return; // We have not filled the buffer, so let's carry on streaming |
| 173 | // We have a full input buffer, so we compressit. |
| 174 | while ( myZStream.avail_in > 0 ) { |
| 175 | deflate ( &myZStream, 0 ); |
| 176 | if ( myZStream.avail_out == 0 ) { |
| 177 | // We filled the output buffer, let's stream it |
| 178 | myUnderlyingStream.write ( ( char* ) myCompressedDataBuffer, MAX_BUFFER ); |
| 179 | myZStream.next_out = myCompressedDataBuffer; |
| 180 | myZStream.avail_out = MAX_BUFFER; |
| 181 | } |
| 182 | // Repeat whilst the input buffer isn't flushed |
| 183 | } |
| 184 | // Now we have flushed the input buffer we can reset it |
| 185 | myZStream.avail_in = 0; |
| 186 | myZStream.next_in = myUncompressedDataBuffer; |
| 187 | } |
| 188 | } |
| 189 | else { |
| 190 | myUnderlyingStream.write ( ( const char * ) data, size ); |
| 191 | } |
| 192 | #endif |
| 193 | } |
| 194 | |
| 195 | void Engine::readBinary(uint8* data, uint32 size) THROWS (PersistException) |
| 196 | { |
| 197 | if ( myOperationalMode != modeRead ) |
| 198 | THROW (PersistException( "Cannot read from an output Engine" )); |
| 199 | #ifdef NO_COMPRESSION |
| 200 | myUnderlyingStream.read((char *)data,size); |
| 201 | #else |
| 202 | if ( use_compression ) { |
| 203 | uint32 read = 0; |
| 204 | while ( read < size ) { |
| 205 | // If we have any data left in the uncompressed buffer - use it |
| 206 | if ( myLastUncompressedDataRead < myZStream.next_out ) { |
| 207 | uint32 toRead = size - read; |
| 208 | if ( toRead > ( uint32 ) ( myZStream.next_out - myLastUncompressedDataRead ) ) |
| 209 | toRead = ( myZStream.next_out - myLastUncompressedDataRead ); |
| 210 | memcpy ( data + read, myLastUncompressedDataRead, toRead ); |
| 211 | myLastUncompressedDataRead += toRead; |
| 212 | read += toRead; |
| 213 | } |
| 214 | if ( read == size ) |
| 215 | return; // We have read all we need to |
| 216 | // Reset the stream for the next block of data |
| 217 | myLastUncompressedDataRead = myUncompressedDataBuffer; |
| 218 | myZStream.next_out = myUncompressedDataBuffer; |
| 219 | myZStream.avail_out = MAX_BUFFER; |
| 220 | // Next we have to deal such that, until we have a full output buffer, |
| 221 | // (Or we run out of input) |
| 222 | if ( myUnderlyingStream.good() ) { |
| 223 | while ( myUnderlyingStream.good() && myZStream.avail_out > 0 ) { |
| 224 | // Right then, if we have run out of input, fetch another chunk |
| 225 | if ( myZStream.avail_in == 0 ) { |
| 226 | myZStream.next_in = myCompressedDataBuffer; |
| 227 | myUnderlyingStream.read ( ( char* ) myCompressedDataBuffer, MAX_BUFFER ); |
| 228 | myZStream.avail_in = myUnderlyingStream.gcount(); |
| 229 | } |
| 230 | inflate ( &myZStream, 0 ); |
| 231 | } |
| 232 | } |
| 233 | else |
| 234 | { |
| 235 | // Oh dear - we ran out of input on the buffer. |
| 236 | // Maybe we can still inflate some |
| 237 | inflate ( &myZStream, 0 ); |
| 238 | if ( myZStream.avail_out == MAX_BUFFER ) |
| 239 | THROW ( PersistException ( String ( "Oh dear - ran out of input" ) ) ); |
| 240 | } |
| 241 | } |
| 242 | } |
| 243 | else { |
| 244 | myUnderlyingStream.read ( ( char * ) data, size ); |
| 245 | } |
| 246 | #endif |
| 247 | } |
| 248 | |
| 249 | /* |
| 250 | * note, does not (yet?) throw an exception, but interface |
| 251 | * prepared .. |
| 252 | */ |
| 253 | void Engine::write(const BaseObject *object) THROWS (PersistException) |
| 254 | { |
| 255 | // Pre-step, if object is NULL, then don't serialise it - serialise a |
| 256 | // marker to say that it is null. |
| 257 | // as ID's are uint32's, NullObject will do nicely for the task |
| 258 | if (object == NULL) { |
| 259 | uint32 id = NullObject; |
| 260 | write(id); |
| 261 | return; |
| 262 | } |
| 263 | |
| 264 | // First off - has this Object been serialised already? |
| 265 | ArchiveMap::const_iterator itor = myArchiveMap.find(object); |
| 266 | if (itor == myArchiveMap.end()) { |
| 267 | // Unfortunately we need to serialise it - here we go .... |
| 268 | uint32 id = (uint32)myArchiveMap.size(); |
| 269 | myArchiveMap[object] = id; // bumps id automatically for next one |
| 270 | write(id); |
| 271 | ClassMap::const_iterator classItor = myClassMap.find(object->getPersistenceID()); |
| 272 | if (classItor == myClassMap.end()) { |
| 273 | uint32 classId = (uint32)myClassMap.size(); |
| 274 | myClassMap[object->getPersistenceID()] = classId; |
| 275 | write(classId); |
| 276 | write(static_cast<String>(object->getPersistenceID())); |
| 277 | } |
| 278 | else { |
| 279 | write(classItor->second); |
| 280 | } |
| 281 | String majik; |
| 282 | majik = "OBST"; |
| 283 | write(majik); |
| 284 | object->write(*this); |
| 285 | majik = "OBEN"; |
| 286 | write(majik); |
| 287 | } |
| 288 | else { |
| 289 | // This object has been serialised, so just pop its ID out |
| 290 | write(itor->second); |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | /* |
| 295 | * reads in a BaseObject into a reference (pre-instantiated object) |
| 296 | */ |
| 297 | void Engine::read(BaseObject &object) THROWS (PersistException) |
| 298 | { |
| 299 | uint32 id = 0; |
| 300 | read(id); |
| 301 | if (id == NullObject) |
| 302 | THROW (PersistException("Object Id should not be NULL when unpersisting to a reference")); |
| 303 | |
| 304 | // Do we already have this object in memory? |
| 305 | if (id < myArchiveVector.size()) { |
| 306 | object = *(myArchiveVector[id]); |
| 307 | return; |
| 308 | } |
| 309 | |
| 310 | // Okay - read the identifier for the class in... |
| 311 | // we won't need it later since this object is already allocated |
| 312 | readClass(); |
| 313 | |
| 314 | // Okay then - we can read data straight into this object |
| 315 | readObject(&object); |
| 316 | } |
| 317 | |
| 318 | /* |
| 319 | * reads in a BaseObject into a pointer allocating if the pointer is NULL |
| 320 | */ |
| 321 | void Engine::read(BaseObject *&object) THROWS (PersistException) |
| 322 | { |
| 323 | uint32 id = 0; |
| 324 | read(id); |
| 325 | // Is the ID a NULL object? |
| 326 | if (id == NullObject) { |
| 327 | object = NULL; |
| 328 | return; |
| 329 | } |
| 330 | |
| 331 | // Do we already have this object in memory? |
| 332 | if (id < myArchiveVector.size()) { |
| 333 | object = myArchiveVector[id]; |
| 334 | return; |
| 335 | } |
| 336 | |
| 337 | // Okay - read the identifier for the class in... |
| 338 | String className = readClass(); |
| 339 | |
| 340 | // is the pointer already initialized? if so then no need to reallocate |
| 341 | if (object != NULL) { |
| 342 | readObject(object); |
| 343 | return; |
| 344 | } |
| 345 | |
| 346 | // Create the object (of the relevant type) |
| 347 | object = TypeManager::createInstanceOf(className.c_str()); |
| 348 | if (object) { |
| 349 | // Okay then - we can make this object |
| 350 | readObject(object); |
| 351 | } |
| 352 | else |
| 353 | THROW (PersistException(String("Unable to instantiate object of class ")+className)); |
| 354 | } |
| 355 | |
| 356 | /* |
| 357 | * reads the actual object data in |
| 358 | */ |
| 359 | void Engine::readObject(BaseObject* object) THROWS (PersistException) |
| 360 | { |
| 361 | // Okay then - we can make this object |
| 362 | myArchiveVector.push_back(object); |
| 363 | String majik; |
| 364 | read(majik); |
| 365 | if(majik != String("OBST")) |
| 366 | THROW(PersistException("Missing Start-of-Object marker")); |
| 367 | object->read(*this); |
| 368 | read(majik); |
| 369 | if(majik != String("OBEN")) |
| 370 | THROW(PersistException("Missing End-of-Object marker")); |
| 371 | } |
| 372 | |
| 373 | /* |
| 374 | * reads the class information in |
| 375 | */ |
| 376 | const String Engine::readClass() THROWS (PersistException) |
| 377 | { |
| 378 | // Okay - read the identifier for the class in... |
| 379 | uint32 classId = 0; |
| 380 | read(classId); |
| 381 | String className; |
| 382 | if (classId < myClassVector.size()) { |
| 383 | className = myClassVector[classId]; |
| 384 | } |
| 385 | else { |
| 386 | // Okay the class wasn't known yet - save its name |
| 387 | read(className); |
| 388 | myClassVector.push_back(className); |
| 389 | } |
| 390 | return className; |
| 391 | } |
| 392 | |
| 393 | /* |
| 394 | * note, does not (yet?) throw an exception, but interface |
| 395 | * prepared .. |
| 396 | */ |
| 397 | void Engine::write(const String& str) THROWS (PersistException) |
| 398 | { |
| 399 | uint32 len = (uint32)str.length(); |
| 400 | write(len); |
| 401 | writeBinary((uint8*)str.c_str(),len); |
| 402 | } |
| 403 | |
| 404 | void Engine::read(String& str) THROWS (PersistException) |
| 405 | { |
| 406 | uint32 len = 0; |
| 407 | read(len); |
| 408 | uint8 *buffer = new uint8[len+1]; |
| 409 | readBinary(buffer,len); |
| 410 | buffer[len] = 0; |
| 411 | str = (char*)buffer; |
| 412 | delete[] buffer; |
| 413 | } |
| 414 | |
| 415 | /* |
| 416 | * note, does not (yet?) throw an exception, but interface |
| 417 | * prepared .. |
| 418 | */ |
| 419 | void Engine::write(const std::string& str) THROWS (PersistException) |
| 420 | { |
| 421 | uint32 len = (uint32)str.length(); |
| 422 | write(len); |
| 423 | writeBinary((uint8*)str.c_str(),len); |
| 424 | } |
| 425 | |
| 426 | void Engine::read(std::string& str) THROWS (PersistException) |
| 427 | { |
| 428 | uint32 len = 0; |
| 429 | read(len); |
| 430 | uint8 *buffer = new uint8[len+1]; |
| 431 | readBinary(buffer,len); |
| 432 | buffer[len] = 0; |
| 433 | str = (char*)buffer; |
| 434 | delete[] buffer; |
| 435 | } |
| 436 | |
| 437 | #define CCXX_RE(ar,ob) ar.read(ob); return ar |
| 438 | #define CCXX_WE(ar,ob) ar.write(ob); return ar |
| 439 | |
| 440 | Engine& operator >>( Engine& ar, BaseObject &ob) THROWS (PersistException) {CCXX_RE(ar,ob);} |
| 441 | Engine& operator >>( Engine& ar, BaseObject *&ob) THROWS (PersistException) {CCXX_RE(ar,ob);} |
| 442 | Engine& operator <<( Engine& ar, BaseObject const &ob) THROWS (PersistException) {CCXX_WE(ar,&ob);} |
| 443 | Engine& operator <<( Engine& ar, BaseObject const *ob) THROWS (PersistException) {CCXX_WE(ar,ob);} |
| 444 | |
| 445 | Engine& operator >>( Engine& ar, int8& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 446 | Engine& operator <<( Engine& ar, int8 ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 447 | |
| 448 | Engine& operator >>( Engine& ar, uint8& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 449 | Engine& operator <<( Engine& ar, uint8 ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 450 | |
| 451 | Engine& operator >>( Engine& ar, int16& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 452 | Engine& operator <<( Engine& ar, int16 ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 453 | |
| 454 | Engine& operator >>( Engine& ar, uint16& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 455 | Engine& operator <<( Engine& ar, uint16 ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 456 | |
| 457 | Engine& operator >>( Engine& ar, int32& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 458 | Engine& operator <<( Engine& ar, int32 ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 459 | |
| 460 | Engine& operator >>( Engine& ar, uint32& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 461 | Engine& operator <<( Engine& ar, uint32 ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 462 | |
| 463 | #ifdef HAVE_64_BITS |
| 464 | Engine& operator >>( Engine& ar, int64& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 465 | Engine& operator <<( Engine& ar, int64 ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 466 | |
| 467 | Engine& operator >>( Engine& ar, uint64& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 468 | Engine& operator <<( Engine& ar, uint64 ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 469 | #endif |
| 470 | |
| 471 | Engine& operator >>( Engine& ar, float& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 472 | Engine& operator <<( Engine& ar, float ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 473 | |
| 474 | Engine& operator >>( Engine& ar, double& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 475 | Engine& operator <<( Engine& ar, double ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 476 | |
| 477 | Engine& operator >>( Engine& ar, String& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 478 | Engine& operator <<( Engine& ar, String ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 479 | |
| 480 | Engine& operator >>( Engine& ar, std::string& ob) THROWS (PersistException) {CCXX_RE (ar,ob);} |
| 481 | Engine& operator <<( Engine& ar, std::string ob) THROWS (PersistException) {CCXX_WE (ar,ob);} |
| 482 | |
| 483 | Engine& operator >>( Engine& ar, bool& ob) THROWS (PersistException) { |
| 484 | uint32 a; ar.read(a); ob=a==1;return ar; |
| 485 | } |
| 486 | |
| 487 | Engine& operator <<( Engine& ar, bool ob) THROWS (PersistException) { |
| 488 | uint32 a=ob?1:0; ar.write(a); return ar; |
| 489 | } |
| 490 | |
| 491 | #undef CCXX_RE |
| 492 | #undef CCXX_WE |
| 493 | |
| 494 | #ifdef CCXX_NAMESPACES |
| 495 | } |
| 496 | #endif |
| 497 | |
| 498 | #endif |
| 499 | |
| 500 | /** EMACS ** |
| 501 | * Local variables: |
| 502 | * mode: c++ |
| 503 | * c-basic-offset: 4 |
| 504 | * End: |
| 505 | */ |