/**************************************************************************\ * * This file is part of the Coin 3D visualization library. * Copyright (C) 1998-2007 by Systems in Motion. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * ("GPL") version 2 as published by the Free Software Foundation. * See the file LICENSE.GPL at the root directory of this source * distribution for additional information about the GNU GPL. * * For using Coin with software that can not be combined with the GNU * GPL, and for taking advantage of the additional benefits of our * support services, please contact Systems in Motion about acquiring * a Coin Professional Edition License. * * See http://www.coin3d.org/ for more information. * * Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY. * http://www.sim.no/ sales@sim.no coin-support@coin3d.org * \**************************************************************************/ #include "SoInput_FileInfo.h" #include #include // pow() #ifdef HAVE_CONFIG_H #include #endif // HAVE_CONFIG_H #include #include #include #include #include #include #include // ************************************************************************* const unsigned int READBUFSIZE = 65536*2; // ************************************************************************* SoInput_FileInfo::SoInput_FileInfo(SoInput_Reader * readerptr) { this->reader = readerptr; #if defined(HAVE_THREADS) && defined(SOINPUT_ASYNC_IO) this->mutex = cc_mutex_construct(); this->condvar = cc_condvar_construct(); this->sched = cc_sched_construct(1); this->threadbuf[0] = new char[READBUFSIZE]; this->threadbuf[1] = new char[READBUFSIZE]; this->threadbuflen[0] = -1; this->threadbuflen[1] = -1; this->threadreadidx = 0; this->threadbufidx = 0; this->threadeof = FALSE; this->readbuf = NULL; #else // HAVE_THREADS && SOINPUT_ASYNC_IO this->readbuf = new char[READBUFSIZE]; #endif // !(HAVE_THREADS && SOINPUT_ASYNC_IO) this->readbuflen = 0; this->readbufidx = 0; this->header = NULL; this->headerisread = FALSE; this->ivversion = 0.0f; this->linenr = 1; this->totalread = 0; this->lastputback = -1; this->lastchar = -1; this->eof = FALSE; this->isbinary = FALSE; this->vrml1file = FALSE; this->vrml2file = FALSE; this->prefunc = NULL; this->postfunc = NULL; this->stdinname = ""; this->deletebuffer = NULL; #if defined(HAVE_THREADS) && defined(SOINPUT_ASYNC_IO) if (this->reader) { // schedule two buffer reads cc_sched_schedule(this->sched, sched_cb, this, 0); } #endif // HAVE_THREADS && SOINPUT_ASYNC_IO } SoInput_FileInfo::~SoInput_FileInfo() { #if defined(HAVE_THREADS) && defined(SOINPUT_ASYNC_IO) cc_sched_destruct(this->sched); cc_condvar_destruct(this->condvar); cc_mutex_destruct(this->mutex); delete[] this->threadbuf[0]; delete[] this->threadbuf[1]; #else // HAVE_THREADS && SOINPUT_ASYNC_IO delete[] this->readbuf; #endif // !(HAVE_THREADS && SOINPUT_ASYNC_IO) delete this->reader; // to be safe, delete this after deleting the reader delete[] this->deletebuffer; } #if defined(HAVE_THREADS) && defined(SOINPUT_ASYNC_IO) void SoInput_FileInfo::sched_cb(void * closure) { SoInput_FileInfo * thisp = (SoInput_FileInfo*) closure; cc_mutex_lock(thisp->mutex); if (!thisp->threadeof) { int idx = thisp->threadreadidx; assert(thisp->threadbuflen[idx] == -1); size_t len = thisp->getReader()->readBuffer(thisp->threadbuf[idx], READBUFSIZE); if (len == 0) { thisp->threadeof = TRUE; thisp->threadbuflen[idx] = 0; } else { thisp->threadbuflen[idx] = len; thisp->threadreadidx ^= 1; } } cc_mutex_unlock(thisp->mutex); cc_condvar_wake_one(thisp->condvar); } #endif // HAVE_THREADS && SOINPUT_ASYNC_IO // This function will as a side-effect set the EOF-flag, as can be // queried by SoInput_FileInfo::isEndOfFile(). void SoInput_FileInfo::doBufferRead(void) { // Make sure that we really do need to read more bytes. assert(this->backbuffer.getLength() == 0); assert(this->readbufidx == this->readbuflen); #if defined(HAVE_THREADS) && defined(SOINPUT_ASYNC_IO) cc_mutex_lock(this->mutex); int idx = this->threadbufidx; while (this->threadbuflen[idx] == -1) { cc_condvar_wait(this->condvar, this->mutex); } if (this->threadbuflen[idx] == 0) { this->readbufidx = 0; this->readbuflen = 0; this->eof = TRUE; #if 0 // debug SoDebugError::postInfo("doBufferRead", "met Mr End-of-file"); #endif // debug cc_mutex_unlock(this->mutex); } else { this->totalread += this->readbufidx; this->readbufidx = 0; this->readbuflen = this->threadbuflen[idx]; this->readbuf = this->threadbuf[idx]; this->threadbufidx ^= 1; // make previous buffer ready for new data this->threadbuflen[this->threadbufidx] = -1; if (!this->threadeof) { cc_sched_schedule(this->sched, sched_cb, this, 0); } cc_mutex_unlock(this->mutex); } #else // HAVE_THREADS && SOINPUT_ASYNC_IO size_t len = this->getReader()->readBuffer(this->readbuf, READBUFSIZE); if (len == 0) { this->readbufidx = 0; this->readbuflen = 0; this->eof = TRUE; #if 0 // debug SoDebugError::postInfo("doBufferRead", "met Mr End-of-file"); #endif // debug } else { this->totalread += this->readbufidx; this->readbufidx = 0; this->readbuflen = len; } #endif // !(HAVE_THREADS && SOINPUT_ASYNC_IO) } size_t SoInput_FileInfo::getNumBytesParsedSoFar(void) const { return this->totalread + this->readbufidx - this->backbuffer.getLength(); } SbBool SoInput_FileInfo::getChunkOfBytes(unsigned char * ptr, size_t length) { // Suck out any bytes from the backbuffer first. while ((this->readbufidx == 0) && (this->backbuffer.getLength() > 0) && (length > 0)) { *ptr++ = this->backbuffer.pop(); --length; } do { // Grab bytes from the buffer. while ((this->readbufidx < this->readbuflen) && (length > 0)) { *ptr++ = this->readbuf[this->readbufidx++]; --length; } // Fetch more bytes if necessary. doBufferRead() sets the eof-flag // as a side-effect. if ((length > 0) && !this->eof) { this->doBufferRead(); } } while (length && !this->eof); return !this->eof; } SbBool SoInput_FileInfo::get(char & c) { if ((this->readbufidx == 0) && (this->backbuffer.getLength() > 0)) { c = this->backbuffer.pop(); } else { if (this->readbufidx >= this->readbuflen) { // doBufferRead() sets the EOF flag for the stream if there is // nothing left of the buffer to read. this->doBufferRead(); if (this->eof) { c = (char) EOF; return FALSE; } } c = this->readbuf[this->readbufidx++]; } // NB: the line counting is not working 100% if we start putting // back and re-reading '\r\n' sequences. if ((c == '\r') || ((c == '\n') && (this->lastchar != '\r'))) this->linenr++; this->lastchar = c; this->lastputback = -1; return TRUE; } void SoInput_FileInfo::putBack(const char c) { // Decrease line count if we put back an end-of-line character. // This should take care of Unix-, MSDOS/MSWin- and MacOS-style // generated files. NB: the line counting is not working 100% if // we start putting back and re-reading multiple parts of '\r\n' // sequences. if (!this->isbinary && ((c == '\r') || (c == '\n'))) this->linenr--; this->lastputback = (int)c; this->lastchar = -1; if (this->readbufidx > 0) { --this->readbufidx; // Make sure we write back the same character which was read.. assert(c == this->readbuf[this->readbufidx]); } else { this->backbuffer.append(c); } this->eof = FALSE; } void SoInput_FileInfo::putBack(const char * const str) { assert(!this->isbinary); const int n = int(strlen(str)); if (!n) return; // Decrease line count if we put back any end-of-line // characters. This should take care of Unix-, MSDOS/MSWin- and // MacOS-style generated files. What a mess. for (int i = 0; i < n; ++i) { if ((str[i] == '\r') || ((str[i] == '\n') && (this->lastputback != (int)'\r'))) --this->linenr; this->lastputback = (int)str[i]; } this->lastchar = -1; for (int c = n - 1; c >= 0; --c) { if (this->readbufidx > 0) { --this->readbufidx; #if COIN_DEBUG assert(this->readbuf[this->readbufidx] == str[c]); #endif } else { this->backbuffer.append(str[c]); } } this->eof = FALSE; } SbBool SoInput_FileInfo::skipWhiteSpace(void) { const char COMMENT_CHAR = '#'; while (TRUE) { char c; SbBool gotchar; while ((gotchar = this->get(c)) && this->isSpace(c)); if (!gotchar) return FALSE; if (c == COMMENT_CHAR) { while ((gotchar = this->get(c)) && (c != '\n') && (c != '\r')); if (!gotchar) return FALSE; if (c == '\r') { gotchar = this->get(c); if (!gotchar) return FALSE; if (c != '\n') this->putBack(c); } } else { this->putBack(c); break; } } return TRUE; } // Returns TRUE if an attempt at reading the file header went // without hitting EOF. Check this->ivversion != 0.0f to see if the // header parse actually succeeded. // The SoInput parameter is used in the precallback SbBool SoInput_FileInfo::readHeaderInternal(SoInput * soinput) { this->headerisread = TRUE; this->header = ""; this->ivversion = 0.0f; this->vrml1file = FALSE; this->vrml2file = FALSE; char c; if (!this->get(c)) return FALSE; if (c != '#') { this->putBack(c); return TRUE; } this->header += c; while (this->get(c) && (c != '\n') && (c != '\r')) this->header += c; if (this->eof) return FALSE; if (!SoDB::getHeaderData(this->header, this->isbinary, this->ivversion, this->prefunc, this->postfunc, this->userdata, TRUE)) { this->ivversion = 0.0f; } else { SbString vrml1string("#VRML V1.0 ascii"); SbString vrml2string("#VRML V2.0 utf8"); if (strncmp(vrml1string.getString(), this->header.getString(), vrml1string.getLength()) == 0) { this->vrml1file = TRUE; } else if (strncmp(vrml2string.getString(), this->header.getString(), vrml2string.getLength()) == 0) { this->vrml2file = TRUE; } if (this->prefunc) this->prefunc(this->userdata, soinput); } return TRUE; } void SoInput_FileInfo::connectRoutes(SoInput * in) { const SbName * routeptr = this->routelist.getArrayPtr(); const int n = this->routelist.getLength(); for (int i = 0; i < n; i += 4) { SbName fromnodename = routeptr[i]; SbName fromfieldname = routeptr[i+1]; SbName tonodename = routeptr[i+2]; SbName tofieldname = routeptr[i+3]; SoNode * fromnode = SoNode::getByName(fromnodename); SoNode * tonode = SoNode::getByName(tonodename); if (!fromnode || !tonode) { SoReadError::post(in, "Unable to create ROUTE from %s.%s to %s.%s. " "Couldn't find both node references.", fromnodename.getString(), fromfieldname.getString(), tonodename.getString(), tofieldname.getString()); } else { (void)SoBase::connectRoute(in, fromnodename, fromfieldname, tonodename, tofieldname); } } } // Unrefernce all protos void SoInput_FileInfo::unrefProtos(void) { const int n = this->protolist.getLength(); for (int i = 0; i < n; i++) { this->protolist[i]->unref(); } this->protolist.truncate(0); } // search for PROTO in this SoInput instance SoProto * SoInput_FileInfo::findProto(const SbName & name) { const int n = this->protolist.getLength(); SoProto * const * ptr = this->protolist.getArrayPtr(); for (int i = 0; i < n; i++) { if (ptr[i]->getProtoName() == name) return ptr[i]; } return NULL; } // wrapper around this->reader. We delay creating the reader if we're // reading from stdin (reader == NULL). SoInput_Reader * SoInput_FileInfo::getReader(void) { if (this->reader == NULL) { this->reader = SoInput_Reader::createReader(coin_get_stdin(), SbString("")); #if defined(HAVE_THREADS) && defined(SOINPUT_ASYNC_IO) // schedule a buffer read cc_sched_schedule(this->sched, sched_cb, this, 0); #endif // HAVE_THREADS && SOINPUT_ASYNC_IO } return this->reader; } SbBool SoInput_FileInfo::readUnsignedIntegerString(char * str) { assert(!this->isBinary()); int minSize = 1; char * s = str; if (this->readChar(s, '0')) { if (this->readChar(s + 1, 'x')) { s += 2 + this->readHexDigits(s + 2); minSize = 3; } else s += 1 + this->readDigits(s + 1); } else s += this->readDigits(s); if (s - str < minSize) return FALSE; *s = '\0'; return TRUE; } SbBool SoInput_FileInfo::readUnsignedInteger(uint32_t & l) { assert(!this->isBinary()); // FIXME: fixed size buffer for input of unknown // length. Ouch. 19990530 mortene. char str[512]; if (! this->readUnsignedIntegerString(str)) return FALSE; // FIXME: check man page of strtoul and exploit the functionality // provided better -- it looks like we are duplicating some of the // effort. 19990530 mortene. l = strtoul(str, NULL, 0); return TRUE; } SbBool SoInput_FileInfo::readInteger(int32_t & l) { assert(!this->isBinary()); // FIXME: fixed size buffer for input of unknown // length. Ouch. 19990530 mortene. char str[512]; char * s = str; SbBool minus = FALSE; if (this->readChar(s, '-')) { minus = TRUE; s++; } else if (this->readChar(s, '+')) s++; if (! this->readUnsignedIntegerString(s)) return FALSE; // FIXME: check man page of strtol and exploit the functionality // provided better -- it looks like we are duplicating some of the // effort. 19990530 mortene. #if 1 // old code l = strtol(str, NULL, 0); #else // first version of replacement of strtol. Not activated yet int i, n = strlen(s); if (n >= 3 && s[0] == '0' && s[1] == 'x') { int v = 0; int mul = 1; for (i = 2; i < n; i++) { char c = s[(n-1)-i+2]; if (c >= '0' && c <= '9') { v += (c-'0') * mul; } else if (c >= 'a' && c <= 'f') { v += ((c-'a')+10) * mul; } else { v += ((c-'A')+10) * mul; } mul <<= 4; } l = v; } else { int v = 0; int mul = 1; for (i = 0; i < n; i++) { char c = s[(n-1)-i]; v += (c-'0') * mul; mul *= 10; } l = v; } if (minus) l = -l; #endif // strtol replacement return TRUE; } SbBool SoInput_FileInfo::readReal(double & d) { assert(!this->isBinary()); const int BUFSIZE = 2048; SbBool minus = FALSE; SbBool gotNum = FALSE; int i, n; char str[BUFSIZE]; char * s = str; double number; double exponent; n = this->readChar(s, '-'); if (n == 0) { n = this->readChar(s, '+'); } else minus = TRUE; s += n; if ((n = this->readDigits(s)) > 0) { gotNum = TRUE; number = 0.0; double mul = 1.0; for (i = 0; i < n; i++) { number += (s[(n-1)-i] - '0') * mul; mul *= 10.0; } s += n; } else { number = 0.0; } if (this->readChar(s, '.') > 0) { s++; if ((n = this->readDigits(s)) > 0) { gotNum = TRUE; double mul = 0.1; for (i = 0; i < n; i++) { number += (s[i]-'0') * mul; mul *= 0.1; } s += n; } } if (! gotNum) return FALSE; if (minus) number = -number; n = this->readChar(s, 'e'); if (n == 0) n = this->readChar(s, 'E'); if (n > 0) { s += n; minus = FALSE; n = this->readChar(s, '-'); if (n == 0) { n = this->readChar(s, '+'); } else minus = TRUE; s += n; if ((n = this->readDigits(s)) > 0) { exponent = 0.0; double mul = 1.0; for (i = 0; i < n; i++) { exponent += (s[(n-1)-i]-'0') * mul; mul *= 10.0; } if (minus) exponent = -exponent; number *= pow(10.0, exponent); } else return FALSE; } d = number; return TRUE; } int SoInput_FileInfo::readChar(char * s, char charToRead) { int ret = 0; char c; if (this->get(c)) { if (c == charToRead) { *s = c; ret = 1; } else { this->putBack(c); } } return ret; } int SoInput_FileInfo::readDigits(char * str) { assert(!this->isBinary()); char c, * s = str; while (this->get(c)) { if (isdigit(c)) *s++ = c; else { this->putBack(c); break; } } const ptrdiff_t offset = s - str; return (int)offset; } int SoInput_FileInfo::readHexDigits(char * str) { assert(!this->isBinary()); char c, * s = str; while (this->get(c)) { if (isxdigit(c)) *s++ = c; else { this->putBack(c); break; } } const ptrdiff_t offset = s - str; return (int)offset; }