khanat-opennel-code/code/ryzom/common/src/game_share/persistent_data.cpp

1303 lines
38 KiB
C++

// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
/*
NOTES:
Strings are limited to 256 characters - this is OK for the basic uses of the system - may require review later...
- Note that for many apps use of a string vector in place of a string will solve the 255 limit.
=> deprecated: the test checking the string length has been disabled, which allows string to exceed a 256 char length
*/
//-------------------------------------------------------------------------
// incudes
//-------------------------------------------------------------------------
#include "stdpch.h"
#include "utils.h"
#include "persistent_data.h"
#include "persistent_data_tree.h"
#ifdef NL_OS_WINDOWS
#include <io.h>
#endif
//-------------------------------------------------------------------------
// namespaces
//-------------------------------------------------------------------------
using namespace NLMISC;
using namespace std;
CPdrTokenRegistry *CPdrTokenRegistry::_Instance = NULL;
//-------------------------------------------------------------------------
// globals
//-------------------------------------------------------------------------
CPersistentDataRecord::CArg CPersistentDataRecord::TempArg;
//-------------------------------------------------------------------------
// basics...
//-------------------------------------------------------------------------
// ctor
CPersistentDataRecord::CPersistentDataRecord(const std::string& tokenFamily)
{
// setup the token family
_TokenFamily=tokenFamily;
// clear write data/ properties
clear();
// clear read data/ properties
rewind();
}
//-------------------------------------------------------------------------
// set of accessors for storing data in a CPersistentDataRecord
//-------------------------------------------------------------------------
void CPersistentDataRecord::clear()
{
H_AUTO(CPersistentDataRecordClear);
// clear persistent data buffers
_ArgTable.clear();
_TokenTable.clear();
// setup the string table from the token faimly's string table
_StringTable= CPdrTokenRegistry::getInstance()->getStringTable(_TokenFamily);
// clear working variables and buffers
_WritingStructStack.clear();
_LookupTbls.clear();
// make sure read pointers don't point past end of data
rewind();
// slot '0' in string table is reserved
addString("BAD_STRING");
}
uint16 CPersistentDataRecord::addString(const string& name)
{
// H_AUTO(CPersistentDataRecordAddString);
// store the length of the input string for speed of access (just to help the optimiser do its job)
uint32 len= (uint32)name.size();
//Disabled to allow >=256 char strings.
//DROP_IF(len>=256,"Attempt to add a string of > 256 characters to the string table",return 0);
// depending on the string length choose a well suited algorithm for performing a fast search of the string table
switch(len)
{
case 0:
// run through the existing strings looking for a match
for (uint32 i=(uint32)_StringTable.size();i--;)
{
if (_StringTable[i].empty())
return (uint16)i;
}
break;
case 1:
{
char c0= name[0]; // first and only char of name
// run through the existing strings looking for a match
for (uint32 i=(uint32)_StringTable.size();i--;)
{
const string &s= _StringTable[i];
if (s.size()==len && s[0]==c0)
return (uint16)i;
}
}
break;
case 2:
{
uint16 c01= *(uint16*)&name[0]; // first and only 2 chars of name
// run through the existing strings looking for a match
for (uint32 i=(uint32)_StringTable.size();i--;)
{
const string &s= _StringTable[i];
if (s.size()==len && (*(uint16*)&s[0]==c01) )
return (uint16)i;
}
}
break;
case 3:
{
uint16 c01= *(uint16*)&name[0]; // first 2 chars of name
char c2= name[2]; // third and final char of name
// run through the existing strings looking for a match
for (uint32 i=(uint32)_StringTable.size();i--;)
{
const string &s= _StringTable[i];
if (s.size()==len && (*(uint16*)&s[0]==c01) && s[2]==c2)
return (uint16)i;
}
}
break;
case 4:
{
uint32 c0123= *(uint32*)&name[0]; // first and only 4 chars of name
// run through the existing strings looking for a match
for (uint32 i=(uint32)_StringTable.size();i--;)
{
const string &s= _StringTable[i];
if (s.size()==len && (*(uint32*)&s[0]==c0123) )
return (uint16)i;
}
}
break;
case 5:
case 6:
case 7:
case 8:
{
uint32 cFirst= *(uint32*)&name[0]; // first 4 chars of name
uint32 endOffs=len-4; // offset to last 4 characters of the name
uint32 cLast= *(uint32*)&name[endOffs]; // last 4 chars of name (touch or overlap with first 4 chars)
// run through the existing strings looking for a match
for (uint32 i=(uint32)_StringTable.size();i--;)
{
const string &s= _StringTable[i];
if (s.size()==len && (*(uint32*)&s[0]==cFirst) && (*(uint32*)&s[endOffs]==cLast))
return (uint16)i;
}
}
break;
case 9:
case 10:
case 11:
case 12:
{
uint32 cFirst= *(uint32*)&name[0]; // first 4 chars of name
uint32 cMid= *(uint32*)&name[4]; // middle 4 chars of name (touch first 4 chars)
uint32 endOffs=len-4; // offset to last 4 characters of the name
uint32 cLast= *(uint32*)&name[endOffs]; // last 4 chars of name (touch or overlap with middle 4 chars)
// run through the existing strings looking for a match
for (uint32 i=(uint32)_StringTable.size();i--;)
{
const string &s= _StringTable[i];
if (s.size()==len && (*(uint32*)&s[0]==cFirst) && (*(uint32*)&s[endOffs]==cLast) && (*(uint32*)&s[4]==cMid))
return (uint16)i;
}
}
break;
case 13:
case 14:
case 15:
case 16:
{
uint32 cFirst= *(uint32*)&name[0]; // first 4 chars of name
uint32 cSecond= *(uint32*)&name[4]; // second 4 chars of name (touch first 4 chars)
uint32 cThird= *(uint32*)&name[8]; // third 4 chars of name (touch second 4 chars)
uint32 endOffs=len-4; // offset to last 4 characters of the name
uint32 cLast= *(uint32*)&name[endOffs]; // last 4 chars of name (touch or overlap with third 4 chars)
// run through the existing strings looking for a match
for (uint32 i=(uint32)_StringTable.size();i--;)
{
const string &s= _StringTable[i];
if (s.size()==len && (*(uint32*)&s[0]==cFirst) && (*(uint32*)&s[endOffs]==cLast)
&& (*(uint32*)&s[4]==cSecond) && (*(uint32*)&s[8]==cThird) )
return (uint16)i;
}
}
break;
default:
{
uint32 cFirst= *(uint32*)&name[0]; // first 4 chars of name
uint32 endOffs=len-4; // offset to last 4 characters of the name
uint32* nameEnd= (uint32*)&name[endOffs]; // pointer to the last 4 chars of name
uint32 cLast= *nameEnd; // last 4 chars of name (touch or overlap with middle 4 chars)
// run through the existing strings looking for a match
for (uint32 i=0;i<_StringTable.size();++i)
{
// store a ref to the next string in the string table just to help optimiser do its job
const string &s= _StringTable[i];
// if string lengths or first r last dwords don't match then abort compare
if (s.size()!=len || (*(uint32*)&s[0]!=cFirst) || (*(uint32*)&s[endOffs]!=cLast) )
continue;
uint32* sIt= (uint32*)&s[4]; // iterator for 's' - init to point to second dword of string
uint32* nameIt= (uint32*)&name[4]; // iterator for 'name'
// run through the strings comparing 4 bytes at a time
while (*sIt==*nameIt)
{
++sIt;
++nameIt;
if (nameIt>=nameEnd)
{
return (uint16)i;
}
}
}
}
}
// no match found so add this string to the string table and return its index
{
// H_AUTO(CPersistentDataRecordAddString_NoMatchFound);
uint16 result= (uint16)_StringTable.size();
_StringTable.push_back(name);
BOMB_IF(result==std::numeric_limits<uint16>::max(),"No more room in string table!!!",_StringTable.pop_back());
return result;
}
}
const NLMISC::CSString& CPersistentDataRecord::lookupString(uint32 idx) const
{
// note that the string table size is never less than 1 as entry 0 is pre-set with the 'invalid string' value
BOMB_IF(idx>=_StringTable.size(),"Attempting to access past end of string table",return lookupString(0));
return _StringTable[idx];
}
const NLMISC::CSString& CPersistentDataRecord::getTokenFamily() const
{
return _TokenFamily;
}
//-------------------------------------------------------------------------
// set of accessors for retrieving data from a CPersistentDataRecord
//-------------------------------------------------------------------------
void CPersistentDataRecord::rewind()
{
_ArgOffset=0;
_TokenOffset=0;
_ReadingStructStack.clear();
}
void CPersistentDataRecord::skipStruct()
{
DROP_IF(!isStartOfStruct(), "Attempting to skip a struct whereas next token is not a struct", return);
skipData();
}
void CPersistentDataRecord::skipData()
{
H_AUTO(CPersistentDataRecordSkipData);
// if this is a structure then skip the whole thing
std::vector<uint16> stack;
stack.reserve(16);
do
{
if (isStartOfStruct())
{
stack.push_back(peekNextToken());
popStructBegin(stack.back());
}
else if (isEndOfStruct())
{
popStructEnd(stack.back());
if (!stack.empty())
stack.pop_back();
}
else
{
popNextArg(peekNextToken());
}
}
while (!stack.empty() && !isEndOfData());
}
CPDRLookupTbl* CPersistentDataRecord::getLookupTbl(uint32 id) const
{
return (id>=_LookupTbls.size())? NULL: _LookupTbls[id];
}
void CPersistentDataRecord::setLookupTbl(uint32 id, CPDRLookupTbl* tbl)
{
// if the container is too small for the id then grow it
if (id>=_LookupTbls.size())
{
// make sure the lookup table id is valid
nlassert(id<CPDRLookupTbl::getNumLookupTableClasses());
// grow the container
_LookupTbls.resize(CPDRLookupTbl::getNumLookupTableClasses(),NULL);
}
// make sure we don't already have a lookup table allocated for this slot
nlassert(_LookupTbls[id]==NULL);
// store away the new lookup table
_LookupTbls[id]= tbl;
}
bool CPersistentDataRecord::operator==(const CPersistentDataRecord& other) const
{
#define RESTORE_STATE_VARS {\
_ArgOffset=oldArgOffset; _TokenOffset=oldTokenOffset; _ReadingStructStack=oldRSS;\
other._ArgOffset=otherOldArgOffset; other._TokenOffset=otherOldTokenOffset; other._ReadingStructStack=otherOldRSS;\
}
#define RETURN_FALSE { RESTORE_STATE_VARS return false; }
#define RETURN_TRUE { RESTORE_STATE_VARS return true; }
// record the old values of the state variables (to be restored on exit)
uint32 oldArgOffset(_ArgOffset);
uint32 oldTokenOffset(_TokenOffset);
TReadingStructStack oldRSS(_ReadingStructStack);
uint32 otherOldArgOffset(other._ArgOffset);
uint32 otherOldTokenOffset(other._TokenOffset);
TReadingStructStack otherOldRSS(other._ReadingStructStack);
// reset state variables - this is equivalent to rewind()
_ArgOffset =0;
_TokenOffset =0;
_ReadingStructStack.clear();
other._ArgOffset =0;
other._TokenOffset =0;
other._ReadingStructStack.clear();
// iterate over the tokens in our PDRs comparing them as we go...
while ( !isEndOfData() && !other.isEndOfData() )
{
// make sure basic type info for next token matches on both sides
if ( isStartOfStruct() != other.isStartOfStruct() )
RETURN_FALSE
if ( isEndOfStruct() != other.isEndOfStruct() )
RETURN_FALSE
if ( isTokenWithNoData() != other.isTokenWithNoData() )
RETURN_FALSE
if ( peekNextTokenName() != other.peekNextTokenName() )
RETURN_FALSE
// deal with the token
if (isStartOfStruct())
{
// skip start of struct token
const_cast<CPersistentDataRecord*>(this)->popStructBegin(peekNextToken());
const_cast<CPersistentDataRecord&>(other).popStructBegin(other.peekNextToken());
}
else if (isEndOfStruct())
{
// skip end of struct token
const_cast<CPersistentDataRecord*>(this)->popStructEnd(peekNextToken());
const_cast<CPersistentDataRecord&>(other).popStructEnd(other.peekNextToken());
}
else if (isTokenWithNoData())
{
// skip token with no data
const_cast<CPersistentDataRecord*>(this)->pop(peekNextToken());
const_cast<CPersistentDataRecord&>(other).pop(other.peekNextToken());
}
else
{
// get value for token and convert to text for comparison
// - this allows us to compare sint32(123) with uint8(123) etc correctly
CSString thisValue;
CSString otherValue;
const_cast<CPersistentDataRecord*>(this)->pop(peekNextToken(),thisValue);
const_cast<CPersistentDataRecord&>(other).pop(other.peekNextToken(),otherValue);
if (thisValue!=otherValue)
RETURN_FALSE
}
}
// make sure we're at the end of both data buffers
if ( !isEndOfData() || !other.isEndOfData() )
RETURN_FALSE
// all of the failure tests passed so we can conclude that our structures match
RETURN_TRUE
#undef RETURN_TRUE
#undef RETURN_FALSE
#undef RESTORE_STATE_VARS
}
//-------------------------------------------------------------------------
// debug methods for retrieving info from pdr records
//-------------------------------------------------------------------------
NLMISC::CSString CPersistentDataRecord::getInfo() const
{
H_AUTO(CPersistentDataRecordGetInfo);
return NLMISC::toString("TotalSize=%u TokenCount=%u DataCount=%u StringCount=%u StringSize=%u ValueCount=%u",
totalDataSize(),_TokenTable.size(),_ArgTable.size(),_StringTable.size(),stringDataSize(),getNumValues());
}
NLMISC::CSString CPersistentDataRecord::getInfoAsCSV() const
{
H_AUTO(CPersistentDataRecordGetInfoAsCSV);
return NLMISC::toString("%u,%u,%u,%u,%u,%u",
totalDataSize(),_TokenTable.size(),_ArgTable.size(),_StringTable.size(),stringDataSize(),getNumValues());
}
const NLMISC::CSString& CPersistentDataRecord::getCSVHeaderLine()
{
static NLMISC::CSString headerLine="TotalSize,TokenCount,DataCount,StringCount,StringSize,ValueCount";
return headerLine;
}
uint32 CPersistentDataRecord::getNumValues() const
{
H_AUTO(CPersistentDataRecordGetNumValues);
// setup a counter variable to buil our result
uint32 result=0;
// record the old values of the state variables (to be restored on exit)
uint32 oldArgOffset(_ArgOffset);
uint32 oldTokenOffset(_TokenOffset);
TReadingStructStack oldRSS(_ReadingStructStack);
// reset state variables - this is equivalent to rewind()
_ArgOffset =0;
_TokenOffset =0;
_ReadingStructStack.clear();
// iterate over the tokens in our PDRs comparing them as we go...
while ( !isEndOfData() )
{
uint16 nextToken= peekNextToken();
CArg::TType nextTokenType= peekNextTokenType();
// deal with the token
if (nextTokenType==CArg::STRUCT_BEGIN) // isStartOfStruct()
{
// skip start of struct token
const_cast<CPersistentDataRecord*>(this)->popStructBegin(nextToken);
}
else if (nextTokenType==CArg::STRUCT_END) // isEndOfStruct()
{
// skip end of struct token
const_cast<CPersistentDataRecord*>(this)->popStructEnd(nextToken);
}
else if (nextTokenType==CArg::FLAG) // isTokenWithNoData()
{
// skip token with no data
const_cast<CPersistentDataRecord*>(this)->pop(nextToken);
++result;
}
else
{
// get the next value and discard it immediately
const_cast<CPersistentDataRecord*>(this)->popNextArg(nextToken);
++result;
}
}
// restore the original values of teh state variables
_ArgOffset=oldArgOffset;
_TokenOffset=oldTokenOffset;
_ReadingStructStack=oldRSS;
// return the number of values that we found
return result;
}
//-------------------------------------------------------------------------
// set of accessors for storing a data record to various destinations
//-------------------------------------------------------------------------
// return the buffer size required to store this record
uint32 CPersistentDataRecord::totalDataSize() const
{
uint32 result=0;
result+= sizeof(uint32); // sizeof 'version number' variable
result+= sizeof(uint32); // sizeof 'data buffer size' variable
result+= sizeof(uint32); // sizeof 'number of tokens in the token table' variable
result+= sizeof(uint32); // sizeof 'number of args in the arg table' variable
result+= sizeof(uint32); // sizeof 'number of strings in the string table' variable
result+= sizeof(uint32); // sizeof 'string table data size' variable
result+= (uint32)_TokenTable.size()*sizeof(TToken); // sizeof the token data
result+= (uint32)_ArgTable.size()*sizeof(_ArgTable[0]); // size of the args data
result+= stringDataSize(); // the data size for the strings in the string table
return result;
}
// return the buffer size required to store this record
uint32 CPersistentDataRecord::stringDataSize() const
{
uint32 result=0;
for (uint32 i=0;i<_StringTable.size();++i)
result+=(uint32)_StringTable[i].size()+1; // the data size for the strings in the string table
return result;
}
bool CPersistentDataRecord::toStream(NLMISC::IStream& dest)
{
H_AUTO(CPersistentDataRecordWriteToStream);
#define WRITE(type,what) { type v= (type)(what); dest.serial(v); }
#define WRITE_BUFF(type,what) dest.serialBuffer( (uint8*)&what[0], sizeof(type) * (uint)what.size() )
// mark the amount of data in output stream before we start adding pdr contents
uint32 dataStart= dest.getPos();
// write the header block
WRITE(uint32,0);
WRITE(uint32,totalDataSize());
WRITE(uint32,_TokenTable.size());
WRITE(uint32,_ArgTable.size());
WRITE(uint32,_StringTable.size());
WRITE(uint32,stringDataSize());
// write the tokens
WRITE_BUFF(TToken,_TokenTable);
// write the arguments
WRITE_BUFF(uint32,_ArgTable);
// mark the amount of data in output stream before we start adding string table
uint32 stringTableStart= dest.getPos();
// write the string table data
for (uint32 i=0;i<_StringTable.size();++i)
{
WRITE_BUFF(char,_StringTable[i]);
WRITE(char,0);
}
// make sure the info written to the header corresponds to the reality of data written to file
BOMB_IF(dest.getPos()- stringTableStart!= stringDataSize(), "Error writing pdr string table to output stream", return false);
BOMB_IF(dest.getPos()- dataStart!= totalDataSize(), "Error writing pdr to output stream", return false);
#undef WRITE
return true;
}
bool CPersistentDataRecord::toBuffer(char *dest,uint32 bufferSize)
{
H_AUTO(CPersistentDataRecordWriteToBuffer);
BOMB_IF(bufferSize<totalDataSize(),"Buffer too small to write data to",return false);
uint32 offset=0;
#define WRITE(type,what) { BOMB_IF(offset+sizeof(type)>bufferSize,"Buffer overflow!",return false); *(type*)&dest[offset]= what; offset+=sizeof(type); }
// write the header block
WRITE(uint32,0);
WRITE(uint32,totalDataSize());
WRITE(uint32,(uint32)_TokenTable.size());
WRITE(uint32,(uint32)_ArgTable.size());
WRITE(uint32,(uint32)_StringTable.size());
WRITE(uint32,stringDataSize());
// write the tokens
for (uint32 i=0;i<_TokenTable.size();++i)
WRITE(TToken,_TokenTable[i]);
// write the arguments
for (uint32 i=0;i<_ArgTable.size();++i)
WRITE(uint32,_ArgTable[i]);
// write the string table data
for (uint32 i=0;i<_StringTable.size();++i)
{
for (uint32 j=0;j<_StringTable[i].size();++j)
WRITE(char,_StringTable[i][j]);
WRITE(char,0);
}
#undef WRITE
BOMB_IF(offset!=totalDataSize(),"Buffer size calculation doesn't match with data written",return false);
return true;
}
bool CPersistentDataRecord::toString(std::string& result,TStringFormat stringFormat)
{
H_AUTO(CPersistentDataRecordWriteToString);
switch (stringFormat)
{
case XML_STRING: return toXML(result);
case LINES_STRING: return toLines(reinterpret_cast<CSString&>(result));
}
BOMB("Invalid string format",return false);
}
bool CPersistentDataRecord::toXML(std::string& result)
{
H_AUTO(CPersistentDataRecordWriteToXMLString);
// clear out the result string before we begin
result.clear();
// build the text buffer
rewind();
while (!isEndOfData())
{
if (isStartOfStruct())
{
// start of a structure block...
for (uint32 i=0;i<=_ReadingStructStack.size();++i) result+='\t';
result+= "<";
result+= lookupString(peekNextToken());
result+= ">\n";
popStructBegin(peekNextToken());
}
else if (isEndOfStruct())
{
// end of a structure block...
for (uint32 i=0;i<=_ReadingStructStack.size()-1;++i) result+='\t';
result+= "</";
result+= lookupString(peekNextToken());
result+= ">\n";
popStructEnd(peekNextToken());
}
else if (isTokenWithNoData())
{
// a standard property without value
for (uint32 i=0;i<=_ReadingStructStack.size();++i) result+='\t';
result+= "<";
result+= lookupString(peekNextToken());
result+= " ";
result+= "type=\"";
result+= CArg::Flag().typeName();
result+= "\" value=\"";
result+= "1";
result+= "\"/>\n";
pop(peekNextToken());
}
else
{
// a standard property with value
string token= lookupString(peekNextToken());
string argType= peekNextArg().typeName();
CSString argTxt;
pop(peekNextToken(),argTxt);
for (uint32 i=0;i<=_ReadingStructStack.size();++i) result+='\t';
result+= "<";
result+= token;
result+= " ";
result+= "type=\"";
result+= argType;
result+= "\" value=\"";
result+= argTxt.encodeXML(true);
result+= "\"/>\n";
}
}
result="<xml>\n"+result+"</xml>\n";
// rewind the read pointer 'cos it's at the end of file
rewind();
return true;
}
bool CPersistentDataRecord::toLines(std::string& result)
{
H_AUTO(CPersistentDataRecordWriteToLinesString);
// setup a persistent data tree and have it scan our input buffer
rewind();
CPersistentDataTree pdt;
return pdt.readFromPdr(*this) && pdt.writeToBuffer(reinterpret_cast<NLMISC::CSString&>(result));
}
bool CPersistentDataRecord::writeToBinFile(const char* fileName)
{
H_AUTO(CPersistentDataRecordWriteToBinFile);
// build the buffer
uint32 bufSize= totalDataSize();
vector<char> buffer;
buffer.resize(bufSize);
toBuffer(&buffer[0],bufSize);
// write the buffer to a file
COFile f;
bool open = f.open(fileName);
DROP_IF(!open,NLMISC::toString("Failed to open output file %s",fileName).c_str(),return false);
// write the binary data to file
try
{
f.serialBuffer((uint8*)&buffer[0],bufSize);
}
catch(...)
{
DROP(NLMISC::toString("Failed to write output file: %s",fileName),return false);
}
// rewind the read pointer 'cos it's at the end of file
rewind();
return true;
}
bool CPersistentDataRecord::writeToTxtFile(const char* fileName,TStringFormat stringFormat)
{
H_AUTO(CPersistentDataRecordWriteToTxtFile);
// build the output text buffer
string s;
toString(s,stringFormat);
// write the text buffer to a file
COFile f;
bool open = f.open(fileName);
DROP_IF(!open,NLMISC::toString("Failed to open output file %s",fileName).c_str(),return false);
// write the binary data to file
try
{
f.serialBuffer((uint8*)&s[0],(uint)s.size());
}
catch(...)
{
DROP(NLMISC::toString("Failed to write output file: %s",fileName),return false);
}
// rewind the read pointer 'cos it's at the end of file
rewind();
return true;
}
bool CPersistentDataRecord::writeToFile(const char* fileName,TFileFormat fileFormat)
{
H_AUTO(CPersistentDataRecordWriteToFile);
switch(fileFormat)
{
case BINARY_FILE:
binary_file:
nlinfo("saving binary file: %s",fileName);
return writeToBinFile(fileName);
case XML_FILE:
xml_file:
nlinfo("saving xml file: %s",fileName);
return writeToTxtFile(fileName,XML_STRING);
case LINES_FILE:
lines_file:
nlinfo("saving line-based txt file: %s",fileName);
return writeToTxtFile(fileName,LINES_STRING);
case ANY_FILE:
{
if (CSString(fileName).right(4)==".xml") goto xml_file;
if (CSString(fileName).right(4)==".txt") goto lines_file;
goto binary_file;
}
}
BOMB("Bad file type supplied to writeToFile() - file not saved: "<<fileName,return false);
}
//-------------------------------------------------------------------------
// set of accessors for retrieving a data record from various sources
//-------------------------------------------------------------------------
bool CPersistentDataRecord::fromBuffer(const char *src,uint32 bufferSize)
{
H_AUTO(CPersistentDataRecordFromBuffer);
// the second dword of a bin buffer contains the buffer length so use it to check whether
// this buffer looks like a correct binary buffer.
bool isValidBinary=(bufferSize>24 && *(uint32*)&src[4]==bufferSize);
// ensure that the data is binary... otherwise try to consider it as text
DROP_IF(!isValidBinary,"Failed to parse buffer due to invalid header",return false);
// make sure the persistent data record is cleared out before we fill it with data
clear();
// Must clear the string table, because read from file (and clear() init it with token family)
_StringTable.clear();
uint32 offset=0;
#define READ(type,what) { DROP_IF(offset+sizeof(type)>bufferSize,"PDR ERROR: Buffer overflow reading: " #type " " #what, clear(); return false); what=*(const type*)&src[offset]; offset+=sizeof(type); }
#define READBUF(type,count,what) { DROP_IF(offset+sizeof(type)*count>bufferSize,"PDR ERROR: Buffer overflow reading buffer: " #what, clear(); return false); if(count>0) { memcpy(what,&src[offset],sizeof(type)*count); offset+=sizeof(type)*count; } }
// READ the header block
uint32 version; READ(uint32,version);
uint32 totalSize; READ(uint32,totalSize);
uint32 tokenCount; READ(uint32,tokenCount);
uint32 argCount; READ(uint32,argCount);
uint32 stringCount; READ(uint32,stringCount);
uint32 stringsSize; READ(uint32,stringsSize);
DROP_IF(version>0,"PDR ERROR: Wrong file format version!",clear();return false);
DROP_IF(totalSize!=bufferSize,"PDR ERROR: Invalid source data",clear();return false);
DROP_IF(totalSize!=offset+tokenCount*sizeof(TToken)+argCount*sizeof(uint32)+stringsSize,"PDR ERROR: Invalid source data",clear();return false);
// READ the tokens
{
H_AUTO(CPersistentDataRecordFromBufferTokenTable)
_TokenTable.resize(tokenCount);
READBUF(TToken,tokenCount,&_TokenTable[0]);
}
// READ the arguments
{
H_AUTO(CPersistentDataRecordFromBufferArgTable)
_ArgTable.resize(argCount);
READBUF(uint32,argCount,&_ArgTable[0]);
}
// READ the string table data
_StringTable.resize(stringCount);
DROP_IF( (stringsSize==0) != (stringCount==0) , "PDR ERROR: Invalid string table parameters", clear(); return false);
if (stringCount!=0)
{
H_AUTO(CPersistentDataRecordFromBufferStringTable)
TStringTable::iterator stringTableIt= _StringTable.begin();
TStringTable::iterator stringTableEnd= _StringTable.end();
const char* stringDataIt= (const char*)&src[offset];
const char* stringDataEnd= (const char*)&src[offset+stringsSize];
DROP_IF(stringDataEnd[-1]!=0,"PDR ERROR: Last string table entry isn't zero terminated", clear(); return false);
do
{
// prepare to push a new string into the string table
NLMISC::CSString& theTableEntry= *stringTableIt;
++stringTableIt;
// copy out the string
{
H_AUTO(CPersistentDataRecordFromBufferStringCopy)
theTableEntry= stringDataIt;
}
// update the string start marker
stringDataIt+= theTableEntry.size()+1;
// make sure we haven't run out of string data or string slots
DROP_IF( (stringTableIt!=stringTableEnd) != (stringDataIt!=stringDataEnd), "PDR ERROR: Invalid string table", clear(); return false);
}
while (stringDataIt!=stringDataEnd);
DROP_IF(stringTableIt!=stringTableEnd,"PDR ERROR: Too few strings found in string table",clear(); return false);
offset+= stringsSize;
}
#undef READBUF
#undef READ
BOMB_IF(offset!=totalSize,"Buffer size calculation doesn't match with data written",return false);
return true;
}
std::string calculateLineAndColumn(const CSString& buff,uint32 index)
{
uint32 line=1;
uint32 col=1;
for (uint32 i=0;i<index;++i)
{
switch(buff[i])
{
case '\r': break; // ignore cr
case '\n': ++line; col=0; break; // treat lf
case '\t': col=(col+4)&~3; break; // assume 4 point tab
default: ++col; break;
}
}
return NLMISC::toString("line %u: col %u: ",line,col);
}
bool CPersistentDataRecord::fromXML(const std::string& s)
{
H_AUTO(CPersistentDataRecordFromXML);
// we need to treat our input data as a CSString - we static cast because CSStrings are not supposed
// to contain any data of their own - they are just a fonctionality wrapper round a std::string
nlctassert(sizeof(string)==sizeof(CSString));
const CSString& buff= static_cast<const CSString&>(s);
uint32 len=(uint32)s.size();
// make sure the persistent data record is cleared out before we fill it with data
clear();
// Must clear the string table, because read from file (and clear() init it with token family)
_StringTable.clear();
// we have a buffer of xml-like blocks so we're going to start by chunking it up (from the back)
vector<CSString> clauses;
bool clauseOpen=false;
uint32 clauseEnd = 0;
for (uint32 i=len;i--;)
{
switch(buff[i])
{
case '\n': case ' ': case '\t': case '\r': case 26: break;
case '>':
DROP_IF(clauseOpen==true,calculateLineAndColumn(buff,i)+"Found 2 '>'s with no matching '<'",return false);
clauseOpen=true;
clauseEnd=i;
break;
case '<':
DROP_IF(clauseOpen==false,calculateLineAndColumn(buff,i)+"Found '<' with no matching '>'",return false);
clauses.push_back(buff.substr(i+1,clauseEnd-i-1));
clauseOpen=false;
break;
default:
DROP_IF((uint8)(buff[i])<32,calculateLineAndColumn(buff,i)+NLMISC::toString("Invalid (non-ascii text) character encountered: %i",buff[i]),return false);
break;
}
}
DROP_IF(clauses.size()<2||clauses[0]!="/xml"||clauses.back()!="xml","Invalid data file - didn't find <xml>..</xml> structure",return false)
// run through the set of clauses to add them to the data block...
for (uint32 i=(uint32)clauses.size()-1;--i;)
{
// clauses are of four types: <...>=struct_begin </..>=struct_end, <../>=prop, <!..>=comment
if (clauses[i].left(1)=="!" || clauses[i].left(1)=="?")
{
// comment
}
else if (clauses[i].left(1)=="/")
{
// struct end
CSString ss= clauses[i].leftCrop(1);
pushStructEnd(addString(ss));
}
else if (clauses[i].right(1)=="/")
{
// prop
CSString s= clauses[i].rightCrop(1);
CSString token= s.firstWord(true).strip();
CSString keyword0= s.strtok("=").strip();
CSString value0= s.firstWordOrWords(true,false,false);
s=s.strip();
CSString keyword1= s.strtok("=").strip();
CSString value1= s.firstWordOrWords(true,false,false);
if (keyword0=="value" && keyword1=="type")
{
swap(value0,value1);
swap(keyword0,keyword1);
}
DROP_IF(keyword0!="type" || keyword1!="value","Expecting 'type' and 'value' in property - but not found",continue);
CArg arg(value0,value1.decodeXML(),*this);
push(addString(token),arg);
}
else
{
// struct begin
pushStructBegin(addString(clauses[i]));
}
}
return true;
}
bool CPersistentDataRecord::fromLines(const std::string& inputBuffer)
{
H_AUTO(CPersistentDataRecordFromLines);
// make sure the persistent data record is cleared out before we fill it with data
clear();
// Must clear the string table, because read from file (and clear() init it with token family)
_StringTable.clear();
// setup a persistent data tree and have it scan our input buffer
CPersistentDataTree pdt;
return pdt.readFromBuffer(reinterpret_cast<const NLMISC::CSString&>(inputBuffer)) && pdt.writeToPdr(*this);
}
bool CPersistentDataRecord::fromString(const std::string& s)
{
H_AUTO(CPersistentDataRecordFromString);
// start by skipping any blank characters at the start of file
uint32 i;
for (i=0;i<s.size() && CSString::isWhiteSpace(s[i]);++i)
{}
// make sure there are non blank characters in the string
DROP_IF(i==s.size(),"string is empty",return false);
// determine whether we have an xml string or a txt string
// we assume that all xml files begin with a '<' character
if (s[i]=='<')
return fromXML(s);
else
return fromLines(s);
}
bool CPersistentDataRecord::fromStream(NLMISC::IStream& stream, uint32 size)
{
H_AUTO(pdrFromStream)
// setup a string as a buffer to hold the stream data
CSString buff;
buff.resize(size);
// read the file data
try
{
stream.serialBuffer((uint8*)&buff[0],(uint)buff.size());
}
catch(...)
{
DROP(NLMISC::toString("Failed to read stream input"),return false);
}
// the second dword of a bin buffer contains the buffer length so use it to check whether
// this buffer looks like a correct binary buffer.
bool isBinary=(size>8 && *(uint32*)&buff[4]==size);
if (isBinary) return fromBuffer(&buff[0],size);
// it's not a valid binary file so see whether it looks like a valid text file
DROP_IF(!buff.isValidText(),"File is binary but 'file size' header entry doesn't match true file size",return false);
// parse the data as text...
return fromString(buff);
}
// read from a CMemStream (maybe either binary or text data)
bool CPersistentDataRecord::fromBuffer(NLMISC::IStream& stream)
{
H_AUTO(pdrFromBuffer)
// try with a CMemStream
CMemStream *memStream = dynamic_cast<CMemStream*>(&stream);
if (memStream != NULL)
{
return fromBuffer((const char *)(memStream->buffer()+memStream->getPos()), memStream->length()-memStream->getPos());
}
// try with a IFile
NLMISC::CIFile *fileStream = dynamic_cast<NLMISC::CIFile*>(&stream);
if (fileStream != NULL)
{
return fromStream(*fileStream, fileStream->getFileSize());
}
return false;
}
bool CPersistentDataRecord::readFromFile(const char* fileName)
{
H_AUTO(pdrReadFromFile)
#ifdef NL_OS_WINDOWS
// open the file
FILE* inf= fopen(fileName,"rb");
DROP_IF( inf==NULL, "Failed to open input file "<<fileName, return false);
// get the file size
uint32 length= filelength(fileno(inf));
// allocate a buffer
CSString buffer;
buffer.resize(length);
// read the data
uint32 blocksRead= (uint32)fread(&buffer[0],length,1,inf);
fclose(inf);
DROP_IF( blocksRead!=1, "Failed to read data from file "<<fileName, return false);
// test whether our data buffer is binary
bool isBinary=(length>8 && *(uint32*)&buffer[4]==length);
if (isBinary)
{
return fromBuffer(&buffer[0],length);
}
// it's not a valid binary file so see whether it looks like a valid text file
DROP_IF(!buffer.isValidText(),"File is binary but 'file size' header entry doesn't match true file size",return false);
// parse the data as text...
return fromString(buffer);
#else
// open the file
CIFile f;
bool open = f.open(fileName);
DROP_IF( !open, "Failed to open input file "<<fileName, return false);
// get the file size
uint32 len= f.getFileSize();
bool result= fromStream(f, len);
DROP_IF( !result, "Failed to parse input file "<<fileName, return false);
return true;
#endif
}
bool CPersistentDataRecord::readFromBinFile(const char* fileName)
{
H_AUTO(CPersistentDataRecordReadFromBinFile);
// open the file
CIFile f;
bool open = f.open(fileName);
DROP_IF(!open,NLMISC::toString("Failed to open input file %s",fileName).c_str(),return false)
// get the file size
uint32 len=CFile::getFileSize(fileName);
// setup a string as a buffer to hold the file data
string s;
s.resize(len);
// read the file data
try
{
f.serialBuffer((uint8*)&s[0],(uint)s.size());
}
catch(...)
{
DROP(NLMISC::toString("Failed to read input file: %s",fileName),return false);
}
// parse the buffer contents to re-generate the data
bool result= fromBuffer(&s[0],(uint32)s.size());
DROP_IF( !result, "Failed to parse input file "<<fileName, return false);
return true;
}
bool CPersistentDataRecord::readFromTxtFile(const char* fileName)
{
H_AUTO(CPersistentDataRecordReadFromTxtFile);
// open the file
CIFile f;
bool open = f.open(fileName);
DROP_IF(!open,NLMISC::toString("Failed to open input file %s",fileName).c_str(),return false)
// get the file size
uint32 len=CFile::getFileSize(fileName);
// setup a string as a buffer to hold the file data
CSString buff;
buff.resize(len);
// read the file data
try
{
f.serialBuffer((uint8*)&buff[0],(uint)buff.size());
}
catch(...)
{
DROP(NLMISC::toString("Failed to read input file: %s",fileName),return false);
}
// parse the buffer contents to re-generate the data
bool result= fromString(buff);
DROP_IF( !result, "Failed to parse input file "<<fileName, return false);
return true;
}
//-----------------------------------------------------------------------------
// methods & data CPDRLookupTbl
//-----------------------------------------------------------------------------
uint32 CPDRLookupTbl::_NextLookupTblClassId;
CPDRLookupTbl::TEnumValue CPDRLookupTbl::operator[](uint32 idx) const
{
BOMB_IF(idx>=_TheTbl.size(),"ERROR: Failed to retrieve entry from PDR lookup table (idx is out of bounds) - pdr must be corrupt",return -1);
return _TheTbl[idx];
}
uint32 CPDRLookupTbl::getNumLookupTableClasses()
{
return _NextLookupTblClassId;
}
//-----------------------------------------------------------------------------
// methods CPdrTokenRegistry
//-----------------------------------------------------------------------------
CPdrTokenRegistry* CPdrTokenRegistry::getInstance()
{
// first time the method is called instantiate the singleton object
if (_Instance==NULL)
_Instance= new CPdrTokenRegistry;
// return the pointer to our singleton
return _Instance;
}
void CPdrTokenRegistry::releaseInstance()
{
if( _Instance )
delete _Instance;
_Instance = NULL;
}
uint16 CPdrTokenRegistry::addToken(const std::string& family,const std::string& token)
{
// get the map entry correponding to 'family' (or create a new one if need be)
CPersistentDataRecord::TStringTable& stringTable= _Registry[family];
// look for an existing match in the string table
for (uint16 i=0;i<stringTable.size();++i)
{
if (stringTable[i]==token)
return i;
BOMB_IF(i>=8191,"Failed to add more then 8192 static token to a pdr string table",return 0);
}
// append new entry to the string table and return the new index
stringTable.push_back(token);
return (uint16)stringTable.size()-1;
}
const CPersistentDataRecord::TStringTable& CPdrTokenRegistry::getStringTable(const std::string& family)
{
// return the map entry correponding to 'family' (creating a new one if need be)
return _Registry[family];
}
CPdrTokenRegistry::CPdrTokenRegistry()
{
}