Torque3D Documentation / _generateds / persistenceManager.cpp

persistenceManager.cpp

Engine/source/console/persistenceManager.cpp

More...

Public Functions

ConsoleDocClass(PersistenceManager , "@brief this class manages updating SimObjects in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> they were " "created in non-destructively (mostly aimed at datablocks and materials).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Basic scripting <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">interface:\n\n</a>" " - Creation: <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classpersistencemanager/">PersistenceManager</a>(FooManager);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Flag objects as dirty: FooManager.setDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Remove objects from dirty list: FooManager.removeDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - List all currently dirty objects: FooManager.listDirty();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Check <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> see <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> an object is dirty: FooManager.isDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Save dirty objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> their files: FooManager.saveDirty();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@note Dirty objects don'<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1aded116371789db1fd63c90ef00c95a3d">t</a> update their files until saveDirty() is " "called so you can change their properties after you flag them as <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">dirty\n\n</a>" "@note Currently only used by editors, not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> actual game <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">development\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Console\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editors\n</a>" " @internal" )
DefineEngineMethod(PersistenceManager , clearAll , void , () , "()" "Clears all the tracked objects without saving them." )
DefineEngineMethod(PersistenceManager , deleteObjectsFromFile , void , (const char *fileName) , "( fileName )" "Delete all of the objects that are created from the given file." )
DefineEngineMethod(PersistenceManager , getDirtyObject , S32 , (S32 index) , "( index )" "Returns the ith dirty object." )
DefineEngineMethod(PersistenceManager , getDirtyObjectCount , S32 , () , "()" "Returns the number of dirty objects." )
DefineEngineMethod(PersistenceManager , hasDirty , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the manager has dirty objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save." )
DefineEngineMethod(PersistenceManager , isDirty , bool , (const char *objName) , "(SimObject object)" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the <a href="/coding/class/classsimobject/">SimObject</a> is on the dirty list." )
DefineEngineMethod(PersistenceManager , listDirty , void , () , "()" "Prints the dirty list <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the console." )
DefineEngineMethod(PersistenceManager , removeDirty , void , (const char *objName) , "(SimObject object)" "Remove <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classsimobject/">SimObject</a> from the dirty list." )
DefineEngineMethod(PersistenceManager , removeField , void , (const char *objName, const char *fieldName) , "(SimObject object, string fieldName)" "Remove <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specific field from an object declaration." )
DefineEngineMethod(PersistenceManager , removeObjectFromFile , void , (const char *objName, const char *filename) , ("") )
DefineEngineMethod(PersistenceManager , saveDirty , bool , () , "()" "Saves all of the <a href="/coding/class/classsimobject/">SimObject</a>'s on the dirty list <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> their respective files." )
DefineEngineMethod(PersistenceManager , saveDirtyObject , bool , (const char *objName) , "(SimObject object)" "Save <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> dirty <a href="/coding/class/classsimobject/">SimObject</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> it's file." )
DefineEngineMethod(PersistenceManager , setDirty , void , (const char *objName, const char *fileName) , ("") , "(SimObject object, [filename])" "Mark an existing <a href="/coding/class/classsimobject/">SimObject</a> as dirty (will be written out when saveDirty() is called)." )

Detailed Description

Public Functions

ConsoleDocClass(PersistenceManager , "@brief this class manages updating SimObjects in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> they were " "created in non-destructively (mostly aimed at datablocks and materials).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Basic scripting <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">interface:\n\n</a>" " - Creation: <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classpersistencemanager/">PersistenceManager</a>(FooManager);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Flag objects as dirty: FooManager.setDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Remove objects from dirty list: FooManager.removeDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - List all currently dirty objects: FooManager.listDirty();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Check <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> see <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> an object is dirty: FooManager.isDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Save dirty objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> their files: FooManager.saveDirty();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@note Dirty objects don'<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1aded116371789db1fd63c90ef00c95a3d">t</a> update their files until saveDirty() is " "called so you can change their properties after you flag them as <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">dirty\n\n</a>" "@note Currently only used by editors, not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> actual game <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">development\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Console\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editors\n</a>" " @internal" )

DefineEngineMethod(PersistenceManager , clearAll , void , () , "()" "Clears all the tracked objects without saving them." )

DefineEngineMethod(PersistenceManager , deleteObjectsFromFile , void , (const char *fileName) , "( fileName )" "Delete all of the objects that are created from the given file." )

DefineEngineMethod(PersistenceManager , getDirtyObject , S32 , (S32 index) , "( index )" "Returns the ith dirty object." )

DefineEngineMethod(PersistenceManager , getDirtyObjectCount , S32 , () , "()" "Returns the number of dirty objects." )

DefineEngineMethod(PersistenceManager , hasDirty , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the manager has dirty objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save." )

DefineEngineMethod(PersistenceManager , isDirty , bool , (const char *objName) , "(SimObject object)" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the <a href="/coding/class/classsimobject/">SimObject</a> is on the dirty list." )

DefineEngineMethod(PersistenceManager , listDirty , void , () , "()" "Prints the dirty list <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the console." )

DefineEngineMethod(PersistenceManager , removeDirty , void , (const char *objName) , "(SimObject object)" "Remove <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classsimobject/">SimObject</a> from the dirty list." )

DefineEngineMethod(PersistenceManager , removeField , void , (const char *objName, const char *fieldName) , "(SimObject object, string fieldName)" "Remove <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specific field from an object declaration." )

DefineEngineMethod(PersistenceManager , removeObjectFromFile , void , (const char *objName, const char *filename) , ("") )

DefineEngineMethod(PersistenceManager , saveDirty , bool , () , "()" "Saves all of the <a href="/coding/class/classsimobject/">SimObject</a>'s on the dirty list <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> their respective files." )

DefineEngineMethod(PersistenceManager , saveDirtyObject , bool , (const char *objName) , "(SimObject object)" "Save <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> dirty <a href="/coding/class/classsimobject/">SimObject</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> it's file." )

DefineEngineMethod(PersistenceManager , setDirty , void , (const char *objName, const char *fileName) , ("") , "(SimObject object, [filename])" "Mark an existing <a href="/coding/class/classsimobject/">SimObject</a> as dirty (will be written out when saveDirty() is called)." )

IMPLEMENT_CONOBJECT(PersistenceManager )

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2012 GarageGames, LLC
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24#include "persistenceManager.h"
  25#include "console/simSet.h"
  26#include "console/consoleTypes.h"
  27#include "console/engineAPI.h"
  28#include "core/stream/fileStream.h"
  29#include "gui/core/guiTypes.h"
  30#include "materials/customMaterialDefinition.h"
  31#include "ts/tsShapeConstruct.h"
  32#include "sim/netStringTable.h"
  33
  34
  35IMPLEMENT_CONOBJECT(PersistenceManager);
  36
  37ConsoleDocClass( PersistenceManager,
  38            "@brief this class manages updating SimObjects in the file they were "
  39            "created in non-destructively (mostly aimed at datablocks and materials).\n\n"
  40
  41            "Basic scripting interface:\n\n"
  42            "  - Creation: new PersistenceManager(FooManager);\n"
  43            "  - Flag objects as dirty: FooManager.setDirty(<object name or id>);\n"
  44            "  - Remove objects from dirty list: FooManager.removeDirty(<object name or id>);\n"
  45            "  - List all currently dirty objects: FooManager.listDirty();\n"
  46            "  - Check to see if an object is dirty: FooManager.isDirty(<object name or id>);\n"
  47            "  - Save dirty objects to their files: FooManager.saveDirty();\n\n"
  48            "@note Dirty objects don't update their files until saveDirty() is "
  49            "called so you can change their properties after you flag them as dirty\n\n"
  50            "@note Currently only used by editors, not intended for actual game development\n\n"
  51            "@ingroup Console\n"
  52            "@ingroup Editors\n"
  53            "@internal");
  54
  55PersistenceManager::PersistenceManager()
  56{
  57   mCurrentObject = NULL;
  58   mCurrentFile = NULL;
  59
  60   VECTOR_SET_ASSOCIATION(mLineBuffer);
  61
  62   mLineBuffer.reserve(2048);
  63}
  64
  65PersistenceManager::~PersistenceManager()
  66{
  67   mDirtyObjects.clear();
  68}
  69
  70bool PersistenceManager::onAdd()
  71{
  72   if (!Parent::onAdd())
  73      return false;
  74
  75   return true;
  76}
  77
  78void PersistenceManager::onRemove()
  79{
  80   Parent::onRemove();
  81}
  82
  83void PersistenceManager::clearLineBuffer()
  84{
  85   for (U32 i = 0; i < mLineBuffer.size(); i++)
  86   {
  87      dFree( mLineBuffer[ i ] );
  88      mLineBuffer[ i ] = NULL;
  89   }
  90
  91   mLineBuffer.clear();
  92}
  93
  94void PersistenceManager::deleteObject(ParsedObject* object)
  95{
  96   if (object)
  97   {
  98      // Clear up used property memory
  99      for (U32 j = 0; j < object->properties.size(); j++)
 100      {
 101         ParsedProperty& prop = object->properties[j];
 102
 103         if (prop.value)
 104         {
 105            dFree( prop.value );
 106            prop.value = NULL;
 107         }
 108      }
 109
 110      object->properties.clear();
 111
 112      // Delete the parsed object
 113      SAFE_DELETE(object);
 114   }
 115}
 116
 117void PersistenceManager::clearObjects()
 118{
 119   // Clean up the object buffer
 120   for (U32 i = 0; i < mObjectBuffer.size(); i++)
 121      deleteObject(mObjectBuffer[i]);
 122
 123   mObjectBuffer.clear();
 124
 125   // We shouldn't have anything in the object stack
 126   // but let's clean it up just in case
 127   // Clean up the object buffer
 128   for (U32 i = 0; i < mObjectStack.size(); i++)
 129      deleteObject(mObjectStack[i]);
 130
 131   mObjectStack.clear();
 132
 133   // Finally make sure there isn't a current object
 134   deleteObject(mCurrentObject);
 135}
 136
 137void PersistenceManager::clearFileData()
 138{
 139   // Clear the active file name
 140   if (mCurrentFile)
 141   {
 142      dFree( mCurrentFile );
 143      mCurrentFile = NULL;
 144   }
 145
 146   // Clear the file objects
 147   clearObjects();
 148
 149   // Clear the line buffer
 150   clearLineBuffer();
 151
 152   // Clear the tokenizer data
 153   mParser.clear();
 154}
 155
 156void PersistenceManager::clearAll()
 157{
 158   // Clear the file data in case it hasn't cleared yet
 159   clearFileData();
 160
 161   // Clear the dirty object list
 162   mDirtyObjects.clear();
 163
 164   // Clear the remove field list
 165   mRemoveFields.clear();
 166}
 167
 168bool PersistenceManager::readFile(const char* fileName)
 169{
 170   // Clear our previous file buffers just in
 171   // case saveDirtyFile() didn't catch it
 172   clearFileData();
 173
 174   // Handle an object writing out to a new file
 175   if ( !Torque::FS::IsFile( fileName ) )
 176   {
 177      // Set our current file
 178      mCurrentFile = dStrdup(fileName);
 179
 180      return true;
 181   }
 182
 183   // Try to open the file
 184   FileStream  stream;
 185   stream.open( fileName, Torque::FS::File::Read );
 186
 187   if ( stream.getStatus() != Stream::Ok )
 188   {
 189      Con::errorf("PersistenceManager::readFile() - Failed to open %s", fileName);
 190
 191      return false;
 192   }
 193
 194   // The file is good so read it in
 195   mCurrentFile = dStrdup(fileName);
 196
 197   while(stream.getStatus() != Stream::EOS)
 198   {
 199      U8* buffer = ( U8* ) dMalloc( 2048 );
 200      dMemset(buffer, 0, 2048);
 201
 202      stream.readLine(buffer, 2048);
 203
 204      mLineBuffer.push_back((const char*)buffer);
 205   }
 206
 207   // Because of the way that writeLine() works we need to
 208   // make sure we don't have an empty last line or else
 209   // we will get an extra line break
 210   if (mLineBuffer.size() > 0)
 211   {
 212      if (mLineBuffer.last() && mLineBuffer.last()[0] == 0)
 213      {
 214         dFree(mLineBuffer.last());
 215
 216         mLineBuffer.pop_back();
 217      }
 218   }
 219
 220   stream.close();
 221
 222   //Con::printf("Successfully opened and read %s", mCurrentFile);
 223
 224   return true;
 225}
 226
 227void PersistenceManager::killObject()
 228{
 229   // Don't save this object
 230   SAFE_DELETE(mCurrentObject);
 231
 232   // If there is an object in the stack restore it
 233   if (mObjectStack.size() > 0)
 234   {
 235      mCurrentObject = mObjectStack.last();
 236      mObjectStack.pop_back();
 237   }
 238}
 239
 240void PersistenceManager::saveObject()
 241{
 242   // Now that we have all of the data attempt to
 243   // find the corresponding SimObject
 244   mCurrentObject->simObject = Sim::findObject(mCurrentFile, mCurrentObject->endLine + 1);
 245
 246   // Save this object
 247   mObjectBuffer.push_back(mCurrentObject);
 248
 249   mCurrentObject = NULL;
 250
 251   // If there is an object in the stack restore it
 252   if (mObjectStack.size() > 0)
 253   {
 254      mCurrentObject = mObjectStack.last();
 255      mObjectStack.pop_back();
 256   }
 257}
 258
 259void PersistenceManager::parseObject()
 260{
 261   // We *should* already be in position but just in case...
 262   if (!mParser.tokenICmp("new") &&
 263       !mParser.tokenICmp("singleton") &&
 264       !mParser.tokenICmp("datablock"))
 265   {
 266      Con::errorf("PersistenceManager::parseObject() - handed a position that doesn't point to an object \
 267         creation keyword (new, singleton, datablock)");
 268
 269      return;
 270   }
 271
 272   // If there is an object already being parsed then
 273   // push it into the stack to finish later
 274   if (mCurrentObject)
 275      mObjectStack.push_back(mCurrentObject);
 276
 277   mCurrentObject = new ParsedObject;
 278
 279   //// If this object declaration is being assigned to a variable then
 280   //// consider that the "start" of the declaration (otherwise we could
 281   //// get a script compile error if we delete the object declaration)
 282   mParser.regressToken(true);
 283
 284   if (mParser.tokenICmp("="))
 285   {
 286      // Ok, we are at an '='...back up to the beginning of that variable
 287      mParser.regressToken(true);
 288
 289      // Get the startLine and startPosition
 290      mCurrentObject->startLine = mParser.getCurrentLine();
 291      mCurrentObject->startPosition = mParser.getTokenLineOffset();
 292
 293      // Advance back to the object declaration
 294      mParser.advanceToken(true);
 295      mParser.advanceToken(true);
 296   }
 297   else
 298   {
 299      // Advance back to the object declaration
 300      mParser.advanceToken(true);
 301
 302      // Get the startLine and startPosition
 303      mCurrentObject->startLine = mParser.getCurrentLine();
 304      mCurrentObject->startPosition = mParser.getTokenLineOffset();
 305   }
 306
 307   if (mObjectStack.size() > 0)
 308      mCurrentObject->parentObject = mObjectStack.last();
 309
 310   // The next token should be the className
 311   mCurrentObject->className = StringTable->insert(mParser.getNextToken());
 312
 313   // Advance to '('
 314   mParser.advanceToken(true);
 315
 316   if (!mParser.tokenICmp("("))
 317   {
 318      Con::errorf("PersistenceManager::parseObject() - badly formed object \
 319         declaration on line %d - was expecting a '(' character", mParser.getCurrentLine()+1);
 320
 321      // Remove this object without saving it
 322      killObject();
 323
 324      return;
 325   }
 326
 327   // The next token should either be the object name or ')'
 328   mParser.advanceToken(true);
 329
 330   if (mParser.tokenICmp(")"))
 331   {
 332      mCurrentObject->name = StringTable->EmptyString();
 333
 334      mCurrentObject->nameLine = mParser.getCurrentLine();
 335      mCurrentObject->namePosition = mParser.getTokenLineOffset();
 336   }
 337   else
 338   {
 339      mCurrentObject->name = StringTable->insert(mParser.getToken());
 340
 341      mCurrentObject->nameLine = mParser.getCurrentLine();
 342      mCurrentObject->namePosition = mParser.getTokenLineOffset();
 343
 344      // Advance to either ')' or ':'
 345      mParser.advanceToken(true);
 346
 347      if (mParser.tokenICmp(":"))
 348      {
 349         // Advance past the object we are copying from
 350         mParser.advanceToken(true);
 351
 352         // Advance to ')'
 353         mParser.advanceToken(true);
 354      }
 355
 356      if (!mParser.tokenICmp(")"))
 357      {
 358         Con::errorf("PersistenceManager::parseObject() - badly formed object \
 359            declaration on line %d - was expecting a ')' character", mParser.getCurrentLine()+1);
 360
 361         // Remove this object without saving it
 362         killObject();
 363
 364         return;
 365      }
 366   }
 367
 368   // The next token should either be a ';' or a '{'
 369   mParser.advanceToken(true);
 370
 371   if (mParser.tokenICmp(";"))
 372   {
 373      // Save the end line number
 374      mCurrentObject->endLine = mParser.getCurrentLine();
 375
 376      // Save the end position
 377      mCurrentObject->endPosition = mParser.getTokenLineOffset();
 378
 379      // Flag this object as not having braces
 380      mCurrentObject->hasBraces = false;
 381
 382      saveObject(); // Object has no fields
 383
 384      return;
 385   }
 386   else if (!mParser.tokenICmp("{"))
 387   {
 388      Con::errorf("PersistenceManager::parseObject() - badly formed object \
 389         declaration on line %d - was expecting a '{' character", mParser.getCurrentLine()+1);
 390
 391      // Remove this object without saving it
 392      killObject();
 393
 394      return;
 395   }
 396
 397   while (mParser.advanceToken(true))
 398   {
 399      // Check for a subobject
 400      if (mParser.tokenICmp("new") ||
 401          mParser.tokenICmp("singleton") ||
 402          mParser.tokenICmp("datablock"))
 403      {
 404         parseObject();
 405      }
 406
 407      // Check to see if we have a property
 408      if (mParser.tokenICmp("="))
 409      {
 410         // Ok, we are at an '='...back up to find out
 411         // what variable is getting assigned
 412         mParser.regressToken(true);
 413
 414         const char* variable = mParser.getToken();
 415
 416         if (variable && dStrlen(variable) > 0)
 417         {
 418            // See if it is a global or a local variable
 419            if (variable[0] == '%' || variable[0] == '$')
 420            {
 421               // We ignore this variable and go
 422               // back to our previous place
 423               mParser.advanceToken(true);
 424            }
 425            // Could also potentially be a <object>.<variable>
 426            // assignment which we don't care about either
 427            else if (dStrchr(variable, '.'))
 428            {
 429               // We ignore this variable and go
 430               // back to our previous place
 431               mParser.advanceToken(true);
 432            }
 433            // If we made it to here assume it is a variable
 434            // for the current object
 435            else
 436            {
 437               // Create our new property
 438               mCurrentObject->properties.increment();
 439
 440               ParsedProperty& prop = mCurrentObject->properties.last();
 441
 442               // Check to see if this is an array variable
 443               if (dStrlen(variable) > 3 && variable[dStrlen(variable) - 1] == ']')
 444               {
 445                  // The last character is a ']' which *should* mean
 446                  // there is also a corresponding '['
 447                  const char* arrayPosStart = dStrrchr(variable, '[');
 448
 449                  if (!arrayPosStart)
 450                  {
 451                     Con::errorf("PersistenceManager::parseObject() - error parsing array position - \
 452                        was expecting a '[' character");
 453                  }
 454                  else
 455                  {
 456                     // Parse the array position for the variable name
 457                     S32 arrayPos = -1;
 458
 459                     dSscanf(arrayPosStart, "[%d]", &arrayPos);
 460
 461                     // If we got a valid array position then set it
 462                     if (arrayPos > -1)
 463                        prop.arrayPos = arrayPos;
 464
 465                     // Trim off the [<pos>] from the variable name
 466                     char* variableName = dStrdup(variable);
 467                     variableName[arrayPosStart - variable] = 0;
 468
 469                     // Set the variable name to our new shortened name
 470                     variable = StringTable->insert(variableName, true);
 471
 472                     // Cleanup our variableName buffer
 473                     dFree( variableName );
 474                  }
 475               }
 476
 477
 478               // Set back the variable name
 479               prop.name = StringTable->insert(variable, true);
 480
 481               // Store the start position for this variable
 482               prop.startLine     = mParser.getCurrentLine();
 483               prop.startPosition = mParser.getTokenLineOffset();
 484
 485               // Advance back to the '='
 486               mParser.advanceToken(true);
 487
 488               // Sanity check
 489               if (!mParser.tokenICmp("="))
 490                  Con::errorf("PersistenceManager::parseObject() - somehow we aren't \
 491                               pointing at the expected '=' character");
 492               else
 493               {
 494                  // The next token should be the value
 495                  // being assigned to the variable
 496                  mParser.advanceToken(true);
 497
 498                  // Store the line number for this value
 499                  prop.valueLine = mParser.getCurrentLine();
 500
 501                  // Store the values beginning position
 502                  prop.valuePosition = mParser.getTokenLineOffset();
 503
 504                  // Read tokens up to the semicolon.
 505                  // Quoted tokens skip the leading and trailing quote characters. eg.
 506                  // "this" becomes: this
 507                  // "this" TAB "that" becomes: this" TAB "that
 508                  // "this" TAB "that" TAB "other" becomes: this" TAB "that" TAB "other
 509                  String value;
 510                  bool wasQuoted = false;
 511                  while (!mParser.endOfFile() && !mParser.tokenICmp(";"))
 512                  {
 513                     // Join tokens together (skipped first time through when string is empty)
 514                     if (value.length() > 0)
 515                     {
 516                        if (wasQuoted)
 517                           value += "\" ";                  // quoted followed by non-quoted
 518                        else if (mParser.tokenIsQuoted())
 519                           value += " \"";                  // non-quoted followed by quoted
 520                        else
 521                           value += " ";                    // non-quoted followed by non-quoted
 522                     }
 523
 524                     value += mParser.getToken();
 525                     wasQuoted = mParser.tokenIsQuoted();
 526                     mParser.advanceToken(true);
 527                  }
 528
 529                  // TODO: make sure this doesn't leak
 530                  prop.value = dStrdup(value.c_str());
 531
 532                  if (!mParser.tokenICmp(";"))
 533                     Con::errorf("PersistenceManager::parseObject() - badly formed variable "
 534                                  "assignment on line %d - was expecting a ';' character", mParser.getCurrentLine()+1);
 535
 536                  // Store the end position for this variable
 537                  prop.endLine     = mParser.getCurrentLine();
 538                  prop.endPosition = mParser.getTokenLineOffset();
 539                  if (wasQuoted)
 540                     prop.endPosition -= 1;
 541
 542               }
 543            }
 544         }
 545      }
 546
 547      // Check for the end of the object declaration
 548      if (mParser.tokenICmp("}"))
 549      {
 550         // See if the next token is a ';'
 551         mParser.advanceToken(true);
 552
 553         if (mParser.tokenICmp(";"))
 554         {
 555            // Save the end line number
 556            mCurrentObject->endLine = mParser.getCurrentLine();
 557
 558            // Save the end position
 559            mCurrentObject->endPosition = mParser.getTokenLineOffset();
 560
 561            saveObject();
 562
 563            break;
 564         }
 565      }
 566   }
 567}
 568
 569bool PersistenceManager::parseFile(const char* fileName)
 570{
 571   // Read the file into the line buffer
 572   if (!readFile(fileName))
 573      return false;
 574
 575   // Load it into our Tokenizer parser
 576   if (!mParser.openFile(fileName))
 577   {
 578      // Handle an object writing out to a new file
 579      if ( !Torque::FS::IsFile( fileName ) )
 580         return true;
 581
 582      return false;
 583   }
 584
 585   // Set our reserved "single" tokens
 586   mParser.setSingleTokens("(){};=:");
 587
 588   // Search object declarations
 589   while (mParser.advanceToken(true))
 590   {
 591      if (mParser.tokenICmp("new") ||
 592          mParser.tokenICmp("singleton") ||
 593          mParser.tokenICmp("datablock"))
 594      {
 595         parseObject();
 596      }
 597   }
 598
 599   // If we had an object that didn't end properly
 600   // then we could have objects on the stack
 601   while (mCurrentObject)
 602      saveObject();
 603
 604   //Con::errorf("Parsed Results:");
 605
 606   //for (U32 i = 0; i < mObjectBuffer.size(); i++)
 607   //{
 608   //   ParsedObject* parsedObject = mObjectBuffer[i];
 609
 610   //   Con::warnf("   mObjectBuffer[%d]:", i);
 611   //   Con::warnf("      name = %s",      parsedObject->name);
 612   //   Con::warnf("      className = %s", parsedObject->className);
 613   //   Con::warnf("      startLine = %d", parsedObject->startLine + 1);
 614   //   Con::warnf("      endLine   = %d", parsedObject->endLine + 1);
 615
 616   //   //if (mObjectBuffer[i]->properties.size() > 0)
 617   //   //{
 618   //   //   Con::warnf("   properties:");
 619   //   //   for (U32 j = 0; j < mObjectBuffer[i]->properties.size(); j++)
 620   //   //      Con::warnf("      %s = %s;", mObjectBuffer[i]->properties[j].name,
 621   //   //                                   mObjectBuffer[i]->properties[j].value);
 622   //   //}
 623
 624   //   if (!parsedObject->simObject.isNull())
 625   //   {
 626   //      SimObject* simObject = parsedObject->simObject;
 627
 628   //      Con::warnf("      SimObject(%s) %d:", simObject->getName(), simObject->getId());
 629   //      Con::warnf("         declaration line = %d", simObject->getDeclarationLine());
 630   //   }
 631   //}
 632
 633   return true;
 634}
 635
 636S32 PersistenceManager::getPropertyIndex(ParsedObject* parsedObject, const char* fieldName, U32 arrayPos)
 637{
 638   S32 propertyIndex = -1;
 639
 640   if (!parsedObject)
 641      return propertyIndex;
 642
 643   for (U32 i = 0; i < parsedObject->properties.size(); i++)
 644   {
 645      if (dStricmp(fieldName, parsedObject->properties[i].name) == 0 &&
 646          parsedObject->properties[i].arrayPos == arrayPos)
 647      {
 648         propertyIndex = i;
 649         break;
 650      }
 651   }
 652
 653   return propertyIndex;
 654}
 655
 656char* PersistenceManager::getObjectIndent(ParsedObject* object)
 657{
 658   char* indent = Con::getReturnBuffer(2048);
 659   indent[0] = 0;
 660
 661   if (!object)
 662      return indent;
 663
 664   if (object->startLine < 0 || object->startLine >= mLineBuffer.size())
 665      return indent;
 666
 667   const char* line = mLineBuffer[object->startLine];
 668
 669   if (line)
 670   {
 671      const char* nonSpace = line;
 672
 673      U32 strLen = dStrlen(line);
 674
 675      for (U32 i = 0; i < strLen; i++)
 676      {
 677         if (*nonSpace != ' ')
 678            break;
 679
 680         nonSpace++;
 681      }
 682
 683      dStrncpy(indent, line, nonSpace - line);
 684
 685      indent[nonSpace - line] = 0;
 686   }
 687
 688   return indent;
 689}
 690
 691void PersistenceManager::updatePositions(U32 lineNumber, U32 startPos, S32 diff)
 692{
 693   if (diff == 0)
 694      return;
 695
 696   for (U32 i = 0; i < mObjectBuffer.size(); i++)
 697   {
 698      ParsedObject* object = mObjectBuffer[i];
 699
 700      if (object->nameLine == lineNumber && object->namePosition > startPos)
 701         object->namePosition += diff;
 702
 703      if (object->endLine == lineNumber && object->endPosition > startPos)
 704         object->endPosition += diff;
 705
 706      if (lineNumber >= object->startLine && lineNumber <= object->endLine)
 707      {
 708         for (U32 j = 0; j < object->properties.size(); j++)
 709         {
 710            ParsedProperty& prop = object->properties[j];
 711
 712            S32 propStartPos = prop.startPosition;
 713            S32 endPos       = prop.endPosition;
 714            S32 valuePos     = prop.valuePosition;
 715
 716            if (lineNumber == prop.startLine && propStartPos > startPos)
 717            {
 718               propStartPos += diff;
 719
 720               if (propStartPos < 0)
 721                  propStartPos = 0;
 722
 723               prop.startPosition = valuePos;
 724            }
 725            if (lineNumber == prop.endLine && endPos > startPos)
 726            {
 727               endPos += diff;
 728
 729               if (endPos < 0)
 730                  endPos = 0;
 731
 732               prop.endPosition = endPos;
 733            }
 734            if (lineNumber == prop.valueLine && valuePos > startPos)
 735            {
 736               valuePos += diff;
 737
 738               if (valuePos < 0)
 739                  valuePos = 0;
 740
 741               prop.valuePosition = valuePos;
 742            }
 743         }
 744      }
 745   }
 746}
 747
 748void PersistenceManager::updateLineOffsets(U32 startLine, S32 diff, ParsedObject* skipObject)
 749{
 750   if (diff == 0)
 751      return;
 752
 753   if (startLine >= mLineBuffer.size())
 754      return;
 755
 756   if (startLine + diff >= mLineBuffer.size())
 757      return;
 758
 759   // Make sure we don't double offset a SimObject's
 760   // declaration line
 761   SimObjectList updated;
 762
 763   if (skipObject && !skipObject->simObject.isNull())
 764      updated.push_back_unique(skipObject->simObject);
 765
 766   for (U32 i = 0; i < mObjectBuffer.size(); i++)
 767   {
 768      ParsedObject* object = mObjectBuffer[i];
 769
 770      // See if this is the skipObject
 771      if (skipObject && skipObject == object)
 772         continue;
 773
 774      // We can safely ignore objects that
 775      // came earlier in the file
 776      if (object->endLine < startLine)
 777         continue;
 778
 779      if (object->startLine >= startLine)
 780         object->startLine += diff;
 781
 782      if (object->nameLine >= startLine)
 783         object->nameLine += diff;
 784
 785      for (U32 j = 0; j < object->properties.size(); j++)
 786      {
 787         if (object->properties[j].startLine >= startLine)
 788            object->properties[j].startLine += diff;
 789         if (object->properties[j].endLine >= startLine)
 790            object->properties[j].endLine += diff;
 791         if (object->properties[j].valueLine >= startLine)
 792            object->properties[j].valueLine += diff;
 793      }
 794
 795      if (object->endLine >= startLine)
 796         object->endLine += diff;
 797
 798      if (!object->simObject.isNull() &&
 799          object->simObject->getDeclarationLine() > startLine)
 800      {
 801         // Check for already updated SimObject's
 802         U32 currSize = updated.size();
 803         updated.push_back_unique(object->simObject);
 804
 805         if (updated.size() == currSize)
 806            continue;
 807
 808         S32 newDeclLine = object->simObject->getDeclarationLine() + diff;
 809
 810         if (newDeclLine < 0)
 811            newDeclLine = 0;
 812
 813         object->simObject->setDeclarationLine(newDeclLine);
 814      }
 815   }
 816}
 817
 818PersistenceManager::ParsedObject* PersistenceManager::findParentObject(SimObject* object, ParsedObject* parentObject)
 819{
 820   ParsedObject* ret = NULL;
 821
 822   if (!object)
 823      return ret;
 824
 825   // First test for the SimGroup it belongs to
 826   ret = findParsedObject(object->getGroup(), parentObject);
 827
 828   if (ret)
 829      return ret;
 830
 831   // TODO: Test all of the SimSet's that this object belongs to
 832
 833   return ret;
 834}
 835
 836PersistenceManager::ParsedObject* PersistenceManager::findParsedObject(SimObject* object, ParsedObject* parentObject)
 837{
 838   if (!object)
 839      return NULL;
 840
 841   // See if our object belongs to a parent
 842   if (!parentObject)
 843      parentObject = findParentObject(object, parentObject);
 844
 845   // First let's compare the object to the SimObject's that
 846   // we matched to our ParsedObject's when we loaded them
 847   for (U32 i = 0; i < mObjectBuffer.size(); i++)
 848   {
 849      ParsedObject* testObj = mObjectBuffer[i];
 850
 851      if (testObj->simObject == object)
 852      {
 853         // Deal with children objects
 854         if (testObj->parentObject != parentObject)
 855            continue;
 856
 857         return testObj;
 858      }
 859   }
 860
 861   // Didn't find it in our ParsedObject's SimObject's
 862   // so see if we can find one that corresponds to the
 863   // same name and className
 864   const char *originalName = object->getOriginalName();
 865
 866   // Find the corresponding ParsedObject
 867   if (originalName && originalName[0])
 868   {
 869      for (U32 i = 0; i < mObjectBuffer.size(); i++)
 870      {
 871         ParsedObject* testObj = mObjectBuffer[i];
 872
 873         if (testObj->name == originalName)
 874         {
 875            // Deal with children objects
 876            if (testObj->parentObject != parentObject)
 877               continue;
 878
 879            return testObj;
 880         }
 881      }
 882   }
 883
 884   //Check internal names
 885   if (object->getInternalName())
 886   {
 887      for (U32 i = 0; i < mObjectBuffer.size(); i++)
 888      {
 889         ParsedObject* testObj = mObjectBuffer[i];
 890         for (U32 j = 0; j < testObj->properties.size(); j++)
 891         {
 892            const ParsedProperty &prop = testObj->properties[j];
 893
 894            if (  String::compare( prop.name, "internalName" ) == 0 && 
 895               String::compare( prop.value, object->getInternalName() ) == 0 )
 896               return testObj;
 897            else if ( String::compare(prop.name, "internalName") == 0)
 898               break;
 899         }
 900      }
 901   }
 902
 903   return NULL;
 904}
 905
 906void PersistenceManager::updateToken( const U32 lineNumber, const U32 linePosition, const U32 oldValueLen, const char* newValue, bool addQuotes )
 907{
 908   // Make sure we have a valid lineNumber
 909   if (lineNumber < 0 || linePosition < 0 ||
 910       lineNumber >= mLineBuffer.size())
 911      return;
 912
 913   // Grab the line that the value is on
 914   const char* line = mLineBuffer[lineNumber];
 915
 916   U32 newValueLen = ( newValue ) ? dStrlen(newValue) : 0;
 917
 918   // Make sure we have a valid linePosition
 919   if (linePosition >= dStrlen(line) ||
 920       linePosition + oldValueLen > dStrlen(line))
 921      return;
 922
 923   // Get all of the characters up to the value position
 924   U32 preStringLen = linePosition;
 925
 926   bool needQuotes = false;
 927   if( addQuotes && line[ linePosition - 1 ] != '"' )
 928   {
 929      preStringLen ++;
 930      needQuotes = true;
 931   }
 932
 933   char* preString = ( char* ) dMalloc( preStringLen + 1 );
 934   dMemcpy( preString, line, linePosition );
 935
 936   if( needQuotes )
 937   {
 938      preString[ linePosition ] = '"';
 939      preString[ linePosition + 1 ] = 0;
 940   }
 941   else
 942      preString[ linePosition ] = 0;
 943
 944   // Get all of the characters that occur after the value
 945
 946   const char* postStringSrc = line + linePosition + oldValueLen;
 947   U32 postStringLen = dStrlen( postStringSrc );
 948   if( needQuotes )
 949      postStringLen ++;
 950
 951   char* postString = ( char* ) dMalloc( postStringLen + 1 );
 952   if( needQuotes )
 953      postString[ 0 ] = '"';
 954   dStrcpy( &postString[ needQuotes ? 1 : 0 ], postStringSrc, postStringLen + (needQuotes ? 0 : 1) );
 955   postString[ postStringLen ] = 0;
 956
 957   // Calculate the length of our new line
 958   U32 newLineLen = 0;
 959
 960   newLineLen += preStringLen;
 961   newLineLen += newValueLen;
 962   newLineLen += postStringLen;
 963
 964   // Create a buffer for our new line and
 965   // null terminate it
 966   char* newLine = ( char* ) dMalloc( newLineLen + 1 );
 967   newLine[0] = 0;
 968
 969   // Build the new line with the
 970   // preString + newValue + postString
 971   dStrcat(newLine, preString, newLineLen + 1);
 972   if ( newValue )
 973      dStrcat(newLine, newValue, newLineLen + 1);
 974   dStrcat(newLine, postString, newLineLen + 1);
 975
 976   // Clear our existing line
 977   if (mLineBuffer[lineNumber])
 978   {
 979      dFree( mLineBuffer[ lineNumber ] );
 980      mLineBuffer[ lineNumber ] = NULL;
 981   }
 982
 983   // Set the new line
 984   mLineBuffer[lineNumber] = newLine;
 985
 986   // Figure out the size difference of the old value
 987   // and new value in case we need to update any else
 988   // on the line after it
 989   S32 diff = newValueLen - oldValueLen;
 990
 991   // Update anything that is on the line after this that needs
 992   // to change its offsets to reflect the new line
 993   updatePositions(lineNumber, linePosition, diff);
 994
 995   // Clean up our buffers
 996   dFree( preString );
 997   dFree( postString );
 998}
 999
1000const char* PersistenceManager::getFieldValue(SimObject* object, const char* fieldName, U32 arrayPos)
1001{
1002   // Our return string
1003   char* ret = NULL;
1004
1005   // Buffer to hold the string equivalent of the arrayPos
1006   char arrayPosStr[8];
1007   dSprintf(arrayPosStr, 8, "%d", arrayPos);
1008
1009   // Get the object's value
1010   const char *value = object->getDataField(fieldName, arrayPosStr );
1011   if (value)
1012      ret = dStrdup(value);
1013
1014   return ret;
1015}
1016
1017const char* PersistenceManager::createNewProperty(const char* name, const char* value, bool isArray, U32 arrayPos)
1018{
1019   if (!name || !value)
1020      return NULL;
1021
1022   AssertFatal( value[0] != StringTagPrefixByte, "Got tagged string!" );
1023
1024   char* newProp = ( char* ) dMalloc( 2048 );
1025   dMemset(newProp, 0, 2048);
1026
1027   if (value)
1028   {
1029      if (isArray)
1030         dSprintf(newProp, 2048, "%s[%d] = \"%s\";", name, arrayPos, value);
1031      else
1032         dSprintf(newProp, 2048, "%s = \"%s\";", name, value);
1033   }
1034   else
1035   {
1036      if (isArray)
1037         dSprintf(newProp, 2048, "%s[%d] = \"\";", name, arrayPos);
1038      else
1039         dSprintf(newProp, 2048, "%s = \"\";", name);
1040   }
1041
1042   return newProp;
1043}
1044
1045bool PersistenceManager::isEmptyLine(const char* line)
1046{
1047   // Simple test first
1048   if (!line || dStrlen(line) == 0)
1049      return true;
1050
1051   U32 len = dStrlen(line);
1052
1053   for (U32 i = 0; i < len; i++)
1054   {
1055      const char& c = line[i];
1056
1057      // Skip "empty" characters
1058      if (c == ' '  ||
1059          c == '\t' ||
1060          c == '\r' ||
1061          c == '\n')
1062      {
1063         continue;
1064      }
1065
1066      // If we have made it to the an end of the line
1067      // comment then consider this an empty line
1068      if (c == '/')
1069      {
1070         if (i < len - 1)
1071         {
1072            if (line[i + 1] == '/')
1073               return true;
1074         }
1075      }
1076
1077      // If it isn't an "empty" character or a comment then
1078      // we have a valid character on the line and it isn't empty
1079      return false;
1080   }
1081
1082   return true;
1083}
1084
1085void PersistenceManager::removeLine(U32 lineNumber)
1086{
1087   if (lineNumber >= mLineBuffer.size())
1088      return;
1089
1090   if (mLineBuffer[lineNumber])
1091   {
1092      dFree( mLineBuffer[ lineNumber ] );
1093      mLineBuffer[ lineNumber ] = NULL;
1094   }
1095
1096   mLineBuffer.erase(lineNumber);
1097
1098   updateLineOffsets(lineNumber, -1);
1099}
1100
1101void PersistenceManager::removeTextBlock(U32 startLine, U32 endLine, U32 startPos, U32 endPos, bool removeEmptyLines)
1102{
1103   // Make sure we have valid lines
1104   if (startLine >= mLineBuffer.size() || endLine >= mLineBuffer.size())
1105      return;
1106
1107   // We assume that the startLine is before the endLine
1108   if (startLine > endLine)
1109      return;
1110
1111   // Grab the lines (they may be the same)
1112   const char* startLineText = mLineBuffer[startLine];
1113   const char* endLineText   = mLineBuffer[endLine];
1114
1115   // Make sure we have a valid startPos
1116   if (startPos >= dStrlen(startLineText))
1117      return;
1118
1119   // Make sure we have a valid endPos
1120   if (endPos > dStrlen(endLineText))
1121      return;
1122
1123   if (startLine == endLine)
1124   {
1125      // Now let updateToken do the heavy lifting on removing it
1126      updateToken(startLine, startPos, endPos - startPos, "");
1127
1128      // Handle removing an empty lines if desired
1129      if (removeEmptyLines)
1130      {
1131         const char* line = mLineBuffer[startLine];
1132
1133         if (isEmptyLine(line))
1134            removeLine(startLine);
1135      }
1136   }
1137   else
1138   {
1139      // Start with clearing the startLine from startPos to the end
1140      updateToken(startLine, startPos, dStrlen(startLineText + startPos), "");
1141
1142      // Then clear the endLine from beginning to endPos
1143      updateToken(endLine, 0, endPos, "");
1144
1145      // Handle removing an empty endLine if desired
1146      if (removeEmptyLines)
1147      {
1148         const char* line = mLineBuffer[endLine];
1149
1150         if (isEmptyLine(line))
1151            removeLine(endLine);
1152      }
1153
1154      // Handle removing any lines between the startLine and endLine
1155      for (U32 i = startLine + 1; i < endLine; i++)
1156         removeLine(startLine + 1);
1157
1158      // Handle removing an empty startLine if desired
1159      if (removeEmptyLines)
1160      {
1161         const char* line = mLineBuffer[startLine];
1162
1163         if (isEmptyLine(line))
1164            removeLine(startLine);
1165      }
1166   }
1167}
1168
1169void PersistenceManager::removeParsedObject(ParsedObject* parsedObject)
1170{
1171   if (!parsedObject)
1172      return;
1173
1174   if (parsedObject->startLine < 0 || parsedObject->startLine >= mLineBuffer.size())
1175      return;
1176
1177   if (parsedObject->endLine < 0 || parsedObject->startLine >= mLineBuffer.size())
1178      return;
1179
1180   removeTextBlock(parsedObject->startLine,     parsedObject->endLine,
1181                   parsedObject->startPosition, parsedObject->endPosition+1, true); // +1 to remove trailing semicolon as well
1182
1183   parsedObject->parentObject = NULL;
1184   parsedObject->simObject    = NULL;
1185}
1186
1187void PersistenceManager::removeField(const ParsedProperty& prop)
1188{
1189   if (prop.startLine < 0 || prop.startLine >= mLineBuffer.size())
1190      return;
1191
1192   if (prop.endLine < 0 || prop.endLine >= mLineBuffer.size())
1193      return;
1194
1195   S32 endPosition = prop.endPosition+1;                    // +1 to remove trailing semicolon as well
1196   if ((endPosition < dStrlen(mLineBuffer[prop.endLine])) &&
1197       (mLineBuffer[prop.endLine][endPosition] == ';'))     // adjust end position for quoted values (otherwise a trailing semicolon will remain)
1198      endPosition++;
1199
1200  removeTextBlock(prop.startLine, prop.endLine, prop.startPosition, endPosition, true);
1201}
1202
1203U32 PersistenceManager::writeProperties(const Vector<const char*>& properties, const U32 insertLine, const char* objectIndent)
1204{
1205   U32 currInsertLine = insertLine;
1206
1207   for (U32 i = 0; i < properties.size(); i++)
1208   {
1209      const char* prop = properties[i];
1210
1211      if (!prop || dStrlen(prop) == 0)
1212         continue;
1213
1214      U32 len = dStrlen(objectIndent) + dStrlen(prop) + 4;
1215
1216      char* newLine = ( char* ) dMalloc( len );
1217
1218      dSprintf(newLine, len, "%s   %s", objectIndent, prop);
1219
1220      mLineBuffer.insert(currInsertLine, newLine);
1221      currInsertLine++;
1222   }
1223
1224   return currInsertLine - insertLine;
1225}
1226
1227PersistenceManager::ParsedObject* PersistenceManager::writeNewObject(SimObject* object, const Vector<const char*>& properties, const U32 insertLine, ParsedObject* parentObject)
1228{
1229   ParsedObject* parsedObject = new ParsedObject;
1230
1231   parsedObject->name      = object->getName();
1232   parsedObject->className = object->getClassName();
1233   parsedObject->simObject = object;
1234
1235   U32 currInsertLine = insertLine;
1236
1237   // If the parentObject isn't set see if
1238   // we can find it in the file
1239   if (!parentObject)
1240      parentObject = findParentObject(object);
1241
1242   parsedObject->parentObject = parentObject;
1243
1244   char* indent = getObjectIndent(parentObject);
1245
1246   if (parentObject)
1247      dStrcat(indent, "   \0", 2048);
1248
1249   // Write out the beginning of the object declaration
1250   const char* dclToken = "new";
1251
1252   if (dynamic_cast<Material*>(object) ||
1253       dynamic_cast<CustomMaterial*>(object) ||
1254       dynamic_cast<GuiControlProfile*>(object) ||
1255       dynamic_cast<TSShapeConstructor*>(object))
1256      dclToken = "singleton";
1257   else if( dynamic_cast< SimDataBlock* >( object ) )
1258   {
1259      SimDataBlock* db = static_cast<SimDataBlock*>(object);
1260
1261      if( db->isClientOnly() )
1262      {
1263         if( db->getName() && db->getName()[ 0 ] )
1264            dclToken = "singleton";
1265      }
1266      else
1267         dclToken = "datablock";
1268   }
1269
1270   char newLine[ 4096 ];
1271   dMemset(newLine, 0, sizeof( newLine));
1272
1273   // New line before an object declaration
1274   dSprintf(newLine, sizeof( newLine ), "");
1275
1276   mLineBuffer.insert(currInsertLine, dStrdup(newLine));
1277   currInsertLine++;
1278   dMemset(newLine, 0, sizeof( newLine ));
1279
1280   parsedObject->startLine    = currInsertLine;
1281   parsedObject->nameLine     = currInsertLine;
1282   parsedObject->namePosition = dStrlen(indent) + dStrlen(dclToken) + dStrlen(object->getClassName()) + 2;
1283
1284   // Objects that had no name were getting saved out as: Object((null))
1285   if ( object->getName() != NULL )
1286   {
1287      if( object->getCopySource() )
1288         dSprintf(newLine, sizeof( newLine ), "%s%s %s(%s : %s)", indent, dclToken, object->getClassName(), object->getName(),
1289            object->getCopySource() ? object->getCopySource()->getName() : "" );
1290      else
1291         dSprintf(newLine, sizeof( newLine ), "%s%s %s(%s)", indent, dclToken, object->getClassName(), object->getName());
1292   }
1293   else
1294      dSprintf(newLine, sizeof( newLine ), "%s%s %s()", indent, dclToken, object->getClassName() );
1295
1296   mLineBuffer.insert(currInsertLine, dStrdup(newLine));
1297   currInsertLine++;
1298   dMemset(newLine, 0, sizeof( newLine ));
1299
1300   dSprintf(newLine, sizeof( newLine ), "%s{", indent);
1301
1302   mLineBuffer.insert(currInsertLine, dStrdup(newLine));
1303   currInsertLine++;
1304   dMemset(newLine, 0, sizeof( newLine ));
1305
1306   currInsertLine += writeProperties(properties, currInsertLine, indent);
1307
1308   parsedObject->endLine = currInsertLine;
1309   parsedObject->updated = true;
1310
1311   dSprintf(newLine, sizeof( newLine ), "%s};", indent);
1312
1313   mLineBuffer.insert(currInsertLine, dStrdup(newLine));
1314   currInsertLine++;
1315
1316   updateLineOffsets(insertLine, currInsertLine - insertLine, parsedObject);
1317
1318   mObjectBuffer.push_back(parsedObject);
1319
1320   // Update the SimObject to reflect its saved name and declaration line.   
1321   // These values should always reflect what is in the file, even if the object
1322   // has not actually been re-created from an execution of that file yet.
1323   object->setOriginalName( object->getName() );
1324   object->setDeclarationLine( currInsertLine );
1325   
1326   if (mCurrentFile)
1327      object->setFilename(mCurrentFile);
1328
1329   return parsedObject;
1330}
1331
1332void PersistenceManager::updateObject(SimObject* object, ParsedObject* parentObject)
1333{
1334   // Create a default object of the same type
1335   ConsoleObject *defaultConObject = ConsoleObject::create(object->getClassName());
1336   SimObject* defaultObject = dynamic_cast<SimObject*>(defaultConObject);
1337   
1338   // ***Really*** shouldn't happen
1339   if (!defaultObject)
1340      return;
1341
1342   Vector<const char*> newLines;
1343
1344   ParsedObject* parsedObject = findParsedObject(object, parentObject);
1345
1346   // If we don't already have an association between the ParsedObject
1347   // and the SimObject then go ahead and create it
1348   if (parsedObject && parsedObject->simObject.isNull())
1349      parsedObject->simObject = object;
1350      
1351   // Kill all fields on the remove list.
1352   
1353   for( U32 i = 0; i < mRemoveFields.size(); ++ i )
1354   {
1355      RemoveField& field = mRemoveFields[ i ];
1356      if( field.object != object )
1357         continue;
1358         
1359      S32 propertyIndex = getPropertyIndex( parsedObject, field.fieldName, field.arrayPos );
1360      if( propertyIndex != -1 )
1361         removeField( parsedObject->properties[ propertyIndex ] );
1362   }
1363
1364   // Get our field list
1365   const AbstractClassRep::FieldList &list = object->getFieldList();
1366
1367   for(U32 i = 0; i < list.size(); i++)
1368   {
1369      const AbstractClassRep::Field* f = &list[i];
1370
1371      // Skip the special field types.
1372      if ( f->type >= AbstractClassRep::ARCFirstCustomField )
1373         continue;
1374
1375      for(U32 j = 0; S32(j) < f->elementCount; j++)
1376      {
1377         const char* value = getFieldValue(object, f->pFieldname, j);
1378
1379         // Make sure we got a value
1380         if (!value)
1381            continue;
1382
1383         // Let's see if this field is already in the file
1384         S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j);
1385
1386         if (propertyIndex > -1)
1387         {
1388            ParsedProperty& prop = parsedObject->properties[propertyIndex];
1389
1390            // If this field is on the remove list then remove it and continue
1391            if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value))
1392            {
1393               removeField( parsedObject->properties[ propertyIndex ] );
1394               dFree( value );
1395               continue;
1396            }
1397
1398            // Run the parsed value through the console system conditioners so
1399            // that it will better match the data we got back from the object.
1400            const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag);
1401
1402            // If our data doesn't match then we get to update it.
1403            //
1404            // As for copy-sources, we just assume here that if a property setting
1405            // is there in the file, the user does not want it inherited from the copy-source
1406            // even in the case the actual values are identical.
1407            
1408            if( dStricmp(value, evalue) != 0 )
1409            {
1410               if( value[ 0 ] == '\0' &&
1411                   dStricmp( getFieldValue( defaultObject, f->pFieldname, j ), value ) == 0 &&
1412                   ( !object->getCopySource() || dStricmp( getFieldValue( object->getCopySource(), f->pFieldname, j ), value ) == 0 ) )
1413               {
1414                  removeField( prop );
1415               }
1416               else
1417               {
1418                  // TODO: This should be wrapped in a helper method... probably.
1419                  // Detect and collapse relative path information
1420                  if (f->type == TypeFilename ||
1421                     f->type == TypeStringFilename ||
1422                     f->type == TypeImageFilename ||
1423                     f->type == TypePrefabFilename ||
1424                     f->type == TypeShapeFilename)
1425                  {
1426                     char fnBuf[1024];
1427                     Con::collapseScriptFilename(fnBuf, 1024, value);
1428
1429                     updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true);
1430                  }
1431                  else
1432                     updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true);
1433               }
1434            }
1435         }
1436         else
1437         {
1438            // No need to process a removed field that doesn't exist in the file
1439            if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value))
1440            {
1441               dFree( value );
1442               continue;
1443            }
1444            
1445            bool mustUpdate = false;
1446
1447            // If we didn't find the property in the ParsedObject
1448            // then we need to compare against the default value
1449            // for this property and save it out if it is different
1450
1451            const char* defaultValue = getFieldValue(defaultObject, f->pFieldname, j);
1452            if( !defaultValue || dStricmp( value, defaultValue ) != 0 )
1453            {
1454               // Value differs.  Check whether it also differs from the
1455               // value in the copy source if there is one.
1456               
1457               if( object->getCopySource() )
1458               {
1459                  const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j );
1460                  if( !copySourceValue || dStricmp( copySourceValue, value ) != 0 )
1461                     mustUpdate = true;
1462                     
1463                  if( copySourceValue )
1464                     dFree( copySourceValue );
1465               }
1466               else
1467                  mustUpdate = true;
1468            }
1469            else
1470            {
1471               // Value does not differ.  If it differs from the copy source's value,
1472               // though, we still want to write it out as otherwise we'll see the
1473               // copy source's value override us.
1474               
1475               if( object->getCopySource() )
1476               {
1477                  const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j );
1478                  if( copySourceValue && dStricmp( copySourceValue, value ) != 0 )
1479                     mustUpdate = true;
1480                     
1481                  if( copySourceValue )
1482                     dFree( copySourceValue );
1483               }
1484            }
1485
1486            // The default value for most string type fields is
1487            // NULL so we can't just continue here or we'd never ever
1488            // write them out...
1489            //
1490            //if (!defaultValue)
1491            //   continue;
1492
1493            // If the object's value is different from the default
1494            // value then add it to the ParsedObject's newLines                        
1495            if ( mustUpdate )
1496            {
1497               // TODO: This should be wrapped in a helper method... probably.
1498               // Detect and collapse relative path information
1499               if (f->type == TypeFilename ||
1500                   f->type == TypeStringFilename ||
1501                   f->type == TypeImageFilename ||
1502                   f->type == TypePrefabFilename ||
1503                   f->type == TypeShapeFilename)
1504               {
1505                  char fnBuf[1024];
1506                  Con::collapseScriptFilename(fnBuf, 1024, value);
1507
1508                  newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j));
1509               }
1510               else
1511                  newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j));              
1512            }
1513
1514            if (defaultValue)
1515               dFree( defaultValue );
1516         }
1517
1518         dFree( value );
1519      }
1520   }
1521
1522   // Handle dynamic fields
1523   SimFieldDictionary* fieldDict = object->getFieldDictionary();
1524
1525   for(SimFieldDictionaryIterator itr(fieldDict); *itr; ++itr)
1526   {
1527      SimFieldDictionary::Entry * entry = (*itr);
1528      if( !entry->value )
1529         continue;
1530
1531      // Let's see if this field is already in the file
1532      S32 propertyIndex = getPropertyIndex(parsedObject, entry->slotName);
1533
1534      if (propertyIndex > -1)
1535      {
1536         ParsedProperty& prop = parsedObject->properties[propertyIndex];
1537
1538         // If this field is on the remove list then remove it and continue
1539         if (findRemoveField(object, entry->slotName) || !object->writeField(entry->slotName, entry->value))
1540         {
1541            removeField( parsedObject->properties[ propertyIndex ] );
1542            continue;
1543         }
1544
1545         if( object->getCopySource() )
1546         {
1547            const char* copySourceFieldValue = object->getCopySource()->getDataField( entry->slotName, NULL );
1548            if( String::compare( copySourceFieldValue, entry->value ) == 0 )
1549            {
1550               removeField( prop );
1551               continue;
1552            }
1553         }
1554
1555         const char* evalue = prop.value;
1556
1557         const char *entryVal = entry->value;
1558         if ( entryVal[0] == StringTagPrefixByte )           
1559            entryVal = gNetStringTable->lookupString( dAtoi( entryVal+1 ) );
1560         else
1561         {
1562            // Run the parsed value through the console system conditioners so
1563            // that it will better match the data we got back from the object.
1564            evalue = Con::getFormattedData(TypeString, evalue);
1565         }
1566
1567         // If our data doesn't match then we get to update it
1568         if (dStricmp(entryVal, evalue) != 0)
1569            updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, entryVal);
1570      }
1571      else
1572      {
1573         // No need to process a removed field that doesn't exist in the file
1574         if (findRemoveField(object, entry->slotName) || !object->writeField(entry->slotName, entry->value))
1575            continue;
1576
1577         if( object->getCopySource() )
1578         {
1579            const char* copySourceFieldValue = object->getCopySource()->getDataField( entry->slotName, NULL );
1580            if( String::compare( copySourceFieldValue, entry->value ) == 0 )
1581               continue;
1582         }
1583
1584         newLines.push_back(createNewProperty(entry->slotName, entry->value));
1585      }
1586   }
1587   
1588   // If we have a parsedObject and the name changed
1589   // then update the parsedObject to the new name.   
1590   // NOTE: an object 'can' have a NULL name which crashes in dStricmp.
1591   if (parsedObject && parsedObject->name != StringTable->insert(object->getName(), true) )
1592   {
1593      StringTableEntry objectName = StringTable->insert(object->getName(), true);
1594
1595      if (parsedObject->name != objectName)
1596      {
1597         // Update the name in the file
1598         updateToken(parsedObject->nameLine, parsedObject->namePosition, dStrlen(parsedObject->name), object->getName());
1599
1600         // Updated the parsedObject's name
1601         parsedObject->name = objectName;
1602
1603         // Updated the object's "original" name to the one that is now in the file
1604         object->setOriginalName(objectName);
1605      }
1606   }
1607
1608   if (parsedObject && newLines.size() > 0)
1609   {
1610      U32 lastPropLine = parsedObject->endLine;
1611
1612      if (parsedObject->properties.size() > 0)
1613         lastPropLine = parsedObject->properties.last().valueLine + 1;
1614
1615      U32 currInsertLine = lastPropLine;
1616
1617      const char* indent = getObjectIndent(parsedObject);
1618
1619      // This should handle adding the opening { to an object
1620      // that formerly did not have {};
1621      if (!parsedObject->hasBraces)
1622      {
1623         updateToken(parsedObject->endLine, parsedObject->endPosition, 1, "\r\n{");
1624
1625         currInsertLine++;
1626      }
1627
1628      currInsertLine += writeProperties(newLines, currInsertLine, indent);
1629
1630      // This should handle adding the opening } to an object
1631      // that formerly did not have {};
1632      if (!parsedObject->hasBraces)
1633      {
1634         U32 len = dStrlen(indent) + 3;
1635         char* newLine = ( char* ) dMalloc( len );
1636
1637         dSprintf(newLine, len, "%s};", indent);
1638
1639         mLineBuffer.insert(currInsertLine, newLine);
1640         currInsertLine++;
1641      }
1642
1643      // Update the line offsets to account for the new lines
1644      updateLineOffsets(lastPropLine, currInsertLine - lastPropLine);
1645   }
1646   else if (!parsedObject)
1647   {
1648      U32 insertLine = mLineBuffer.size();
1649
1650      if (!parentObject)
1651         parentObject = findParentObject(object, parentObject);
1652
1653      if (parentObject && parentObject->endLine > -1)
1654         insertLine = parentObject->endLine;
1655
1656      parsedObject = writeNewObject(object, newLines, insertLine, parentObject);
1657   }
1658
1659   // Clean up the newLines memory
1660   for (U32 i = 0; i < newLines.size(); i++)
1661   {
1662      if (newLines[i])
1663      {
1664         dFree(newLines[i]);
1665         newLines[ i ] = NULL;
1666      }
1667   }
1668
1669   newLines.clear();
1670
1671   SimSet* set = dynamic_cast<SimSet*>(object);
1672
1673   if (set)
1674   {
1675      for(SimSet::iterator i = set->begin(); i != set->end(); i++)
1676      {
1677         SimObject* subObject = (SimObject*)(*i);
1678         updateObject(subObject, parsedObject);
1679      }
1680   }
1681
1682   // Loop through the children of this parsedObject
1683   // If they haven't been updated then assume that they
1684   // don't exist in the file anymore
1685   if (parsedObject)
1686   {
1687      for (S32 i = 0; i < mObjectBuffer.size(); i++)
1688      {
1689         ParsedObject* removeObj = mObjectBuffer[i];
1690
1691         if (removeObj->parentObject == parsedObject && !removeObj->updated)
1692         {
1693            removeParsedObject(removeObj);
1694
1695            mObjectBuffer.erase(i);
1696            i--;
1697
1698            deleteObject(removeObj);
1699         }
1700      }
1701   }
1702
1703   // Flag this as an updated object
1704   if (parsedObject)
1705      parsedObject->updated = true;
1706   
1707   // Cleanup our created default object
1708   delete defaultConObject;
1709}
1710
1711bool PersistenceManager::saveDirtyFile()
1712{
1713   FileStream  stream;
1714   stream.open( mCurrentFile, Torque::FS::File::Write );
1715
1716   if ( stream.getStatus() != Stream::Ok )
1717   {
1718      clearFileData();
1719
1720      return false;
1721   }
1722
1723   for (U32 i = 0; i < mLineBuffer.size(); i++)
1724      stream.writeLine((const U8*)mLineBuffer[i]);
1725
1726   stream.close();
1727
1728   //Con::printf("Successfully opened and wrote %s", mCurrentFile);
1729
1730   //Con::errorf("Updated Results:");
1731
1732   //for (U32 i = 0; i < mObjectBuffer.size(); i++)
1733   //{
1734   //   ParsedObject* parsedObject = mObjectBuffer[i];
1735
1736   //   Con::warnf("   mObjectBuffer[%d]:", i);
1737   //   Con::warnf("      name = %s",      parsedObject->name);
1738   //   Con::warnf("      className = %s", parsedObject->className);
1739   //   Con::warnf("      startLine = %d", parsedObject->startLine + 1);
1740   //   Con::warnf("      endLine   = %d", parsedObject->endLine + 1);
1741
1742   //   //if (mObjectBuffer[i]->properties.size() > 0)
1743   //   //{
1744   //   //   Con::warnf("   properties:");
1745   //   //   for (U32 j = 0; j < mObjectBuffer[i]->properties.size(); j++)
1746   //   //      Con::warnf("      %s = %s;", mObjectBuffer[i]->properties[j].name,
1747   //   //                                   mObjectBuffer[i]->properties[j].value);
1748   //   //}
1749
1750   //   if (!parsedObject->simObject.isNull())
1751   //   {
1752   //      SimObject* simObject = parsedObject->simObject;
1753
1754   //      Con::warnf("      SimObject(%s) %d:", simObject->getName(), simObject->getId());
1755   //      Con::warnf("         declaration line = %d", simObject->getDeclarationLine());
1756   //   }
1757   //}
1758
1759   // Clear our file data
1760   clearFileData();
1761
1762   return true;
1763}
1764
1765S32 QSORT_CALLBACK PersistenceManager::compareFiles(const void* a,const void* b)
1766{
1767   DirtyObject* objectA = (DirtyObject*)(a);
1768   DirtyObject* objectB = (DirtyObject*)(b);
1769
1770   if (objectA->isNull())
1771      return -1;
1772   else if (objectB->isNull())
1773      return 1;
1774
1775   if (objectA->fileName == objectB->fileName)
1776      return objectA->getObject()->getDeclarationLine() - objectB->getObject()->getDeclarationLine();
1777
1778   return dStricmp(objectA->fileName, objectB->fileName);
1779}
1780
1781bool PersistenceManager::setDirty(SimObject* inObject, const char* inFileName)
1782{
1783   // Check if the object is already in the dirty list...
1784   DirtyObject *pDirty = findDirtyObject( inObject );   
1785
1786   // The filename we will save this object to (later)..
1787   String saveFile;
1788
1789   // Expand the script filename if we were passed one.
1790   if ( inFileName )
1791   {
1792      char buffer[4096];
1793      Con::expandScriptFilename( buffer, 4096, inFileName );
1794      saveFile = buffer;
1795   }
1796
1797   // If no filename was passed in, and the object was already dirty,
1798   // we have nothing to do.   
1799   if ( saveFile.isEmpty() && pDirty )
1800      return true;
1801
1802   // Otherwise default to the simObject's filename.
1803   if ( saveFile.isEmpty() )
1804      saveFile = inObject->getFilename();   
1805
1806   // Error if still no filename.
1807   if ( saveFile.isEmpty() )
1808   {
1809      if (inObject->getName())
1810         Con::errorf("PersistenceManager::setDirty() - SimObject %s has no file name associated with it - can not save", inObject->getName());
1811      else
1812         Con::errorf("PersistenceManager::setDirty() - SimObject %d has no file name associated with it - can not save", inObject->getId());
1813
1814      return false;
1815   }
1816
1817   // Update the DirtyObject's fileName if we have it
1818   // else create a new one.
1819
1820   if ( pDirty )
1821      pDirty->fileName = StringTable->insert( saveFile );
1822   else
1823   {    
1824      // Add the newly dirty object.
1825      mDirtyObjects.increment();
1826      mDirtyObjects.last().setObject( inObject );
1827      mDirtyObjects.last().fileName = StringTable->insert( saveFile );
1828   }
1829
1830   return true;
1831}
1832
1833void PersistenceManager::removeDirty(SimObject* object)
1834{
1835   for (U32 i = 0; i < mDirtyObjects.size(); i++)
1836   {
1837      const DirtyObject& dirtyObject = mDirtyObjects[i];
1838
1839      if (dirtyObject.isNull())
1840         continue;
1841
1842      if (dirtyObject.getObject() == object)
1843      {
1844         mDirtyObjects.erase(i);
1845         break;
1846      }
1847   }
1848
1849   for (U32 i = 0; i < mRemoveFields.size(); i++)
1850   {
1851      const RemoveField& field = mRemoveFields[i];
1852
1853      if (field.object != object)
1854         continue;
1855
1856      mRemoveFields.erase(i);
1857
1858      if (i > 0)
1859         i--;
1860   }
1861}
1862
1863void PersistenceManager::addRemoveField(SimObject* object, const char* fieldName)
1864{
1865   // Check to see if this is an array variable
1866   U32 arrayPos = 0;
1867   const char* name = fieldName;
1868
1869   if (dStrlen(fieldName) > 3 && fieldName[dStrlen(fieldName) - 1] == ']')
1870   {
1871      // The last character is a ']' which *should* mean
1872      // there is also a corresponding '['
1873      const char* arrayPosStart = dStrrchr(fieldName, '[');
1874
1875      if (!arrayPosStart)
1876      {
1877         Con::errorf("PersistenceManager::addRemoveField() - error parsing array position - \
1878                      was expecting a '[' character");
1879      }
1880      else
1881      {
1882         // Parse the array position for the variable name
1883         dSscanf(arrayPosStart, "[%d]", &arrayPos);
1884
1885         // Trim off the [<pos>] from the variable name
1886         char* variableName = dStrdup(fieldName);
1887         variableName[arrayPosStart - fieldName] = 0;
1888
1889         // Set the variable name to our new shortened name
1890         name = StringTable->insert(variableName, true);
1891
1892         // Cleanup our variableName buffer
1893         dFree( variableName );
1894      }
1895   }
1896
1897   // Make sure this field isn't already on the list
1898   if (!findRemoveField(object, name, arrayPos))
1899   {
1900      mRemoveFields.increment();
1901
1902      RemoveField& field = mRemoveFields.last();
1903
1904      field.object = object;
1905      field.fieldName = StringTable->insert(name);
1906      field.arrayPos = arrayPos;
1907   }
1908}
1909
1910bool PersistenceManager::isDirty(SimObject* object)
1911{
1912   return ( findDirtyObject( object ) != NULL );
1913}
1914
1915PersistenceManager::DirtyObject* PersistenceManager::findDirtyObject(SimObject* object)
1916{
1917   for (U32 i = 0; i < mDirtyObjects.size(); i++)
1918   {
1919      const DirtyObject& dirtyObject = mDirtyObjects[i];
1920
1921      if (dirtyObject.isNull())
1922         continue;
1923
1924      if (dirtyObject.getObject() == object)
1925         return &mDirtyObjects[i];
1926   }
1927
1928   return NULL;
1929}
1930
1931bool PersistenceManager::findRemoveField(SimObject* object, const char* fieldName, U32 arrayPos)
1932{
1933   for (U32 i = 0; i < mRemoveFields.size(); i++)
1934   {
1935      if (mRemoveFields[i].object == object &&
1936          mRemoveFields[i].arrayPos == arrayPos &&
1937          dStricmp(mRemoveFields[i].fieldName, fieldName) == 0)
1938      {
1939         return true;
1940      }
1941   }
1942
1943   return false;
1944}
1945
1946bool PersistenceManager::saveDirty()
1947{
1948   // Remove any null SimObject's first
1949   for (S32 i = 0; i < mDirtyObjects.size(); i++)
1950   {
1951      const DirtyObject& dirtyObject = mDirtyObjects[i];
1952
1953      if (dirtyObject.isNull())
1954      {
1955         mDirtyObjects.erase(i);
1956         i--;
1957      }
1958   }
1959
1960   // Sort by filename and declaration lines
1961   dQsort(mDirtyObjects.address(), mDirtyObjects.size(), sizeof(DirtyList::value_type), compareFiles);
1962
1963   for (U32 i = 0; i < mDirtyObjects.size(); i++)
1964   {
1965      const DirtyObject& dirtyObject = mDirtyObjects[i];
1966
1967      if (dirtyObject.isNull())
1968         continue;
1969
1970      SimObject* object = dirtyObject.getObject();
1971
1972      if (!mCurrentFile || dStricmp(mCurrentFile, dirtyObject.fileName) != 0)
1973      {
1974         // If mCurrentFile is set then that means we
1975         // changed file names so save our previous one
1976         if (mCurrentFile)
1977            saveDirtyFile();
1978
1979         // Open our new file and parse it
1980         bool success = parseFile(dirtyObject.fileName);
1981
1982         if (!success)
1983         {
1984            const char *name = object->getName();
1985            if (name)
1986            {
1987               Con::errorf("PersistenceManager::saveDirty(): Unable to open %s to save %s %s (%d)",
1988                  dirtyObject.fileName, object->getClassName(), name, object->getId());
1989            }
1990            else
1991            {
1992               Con::errorf("PersistenceManager::saveDirty(): Unable to open %s to save %s (%d)",
1993                  dirtyObject.fileName, object->getClassName(), object->getId());
1994            }
1995
1996            continue;
1997         }
1998      }
1999
2000      // Update this object's properties
2001      //
2002      // An empty script file (with 1 line) gets here with zero
2003      // elements in the linebuffer, so this would prevent us from
2004      // ever writing to it... Or is this test preventing bad things from
2005      // happening if the file didn't exist at all?
2006      //
2007      if (mCurrentFile /*&& mLineBuffer.size() > 0*/)
2008         updateObject(object);
2009   }
2010
2011   // Save out our last file
2012   if (mCurrentFile)
2013      saveDirtyFile();
2014
2015   // Done writing out our dirty objects so reset everything
2016   clearAll();
2017
2018   return true;
2019}
2020
2021bool PersistenceManager::saveDirtyObject(SimObject* object)
2022{
2023   // find our object passed in
2024   for (U32 i = 0; i < mDirtyObjects.size(); i++)
2025   {
2026      const DirtyObject& dirtyObject = mDirtyObjects[i];
2027
2028      if (dirtyObject.isNull())
2029         continue;
2030
2031      if (dirtyObject.getObject() == object)
2032      {
2033         // Open our new file and parse it
2034         bool success = parseFile(dirtyObject.fileName);
2035
2036         if (!success)
2037         {
2038            const char *name = object->getName();
2039            if (name)
2040            {
2041               Con::errorf("PersistenceManager::saveDirtyObject(): Unable to open %s to save %s %s (%d)",
2042                  dirtyObject.fileName, object->getClassName(), name, object->getId());
2043            }
2044            else
2045            {
2046               Con::errorf("PersistenceManager::saveDirtyObject(): Unable to open %s to save %s (%d)",
2047                  dirtyObject.fileName, object->getClassName(), object->getId());
2048            }
2049
2050            return false;
2051         }
2052
2053         // if the file exists then lets update and save
2054         if(mCurrentFile)
2055         {
2056            updateObject(object);
2057            saveDirtyFile();
2058         }
2059         
2060         break;
2061      }
2062   }
2063
2064   // remove this object from the dirty list
2065   removeDirty(object);
2066
2067   return true;
2068}
2069
2070void PersistenceManager::removeObjectFromFile(SimObject* object, const char* fileName)
2071{
2072   if (mCurrentFile)
2073   {
2074      Con::errorf("PersistenceManager::removeObjectFromFile(): Can't remove an object from a \
2075                  file while another is currently opened");
2076
2077      return;
2078   }
2079   
2080   const char* file = object->getFilename();
2081   if (fileName)
2082   {
2083      char buffer[1024];
2084      Con::expandScriptFilename( buffer, 1024, fileName );
2085
2086      file = StringTable->insert(buffer);
2087   }
2088
2089   bool success = false;
2090   
2091   if ( file && file[ 0 ] )
2092      success = parseFile(file);
2093
2094   if (!success)
2095   {
2096      const char *name = object->getName();
2097
2098      String errorNameStr;
2099      if ( name )
2100         errorNameStr = String::ToString( "%s %s (%d)", object->getClassName(), name, object->getId() );
2101      else
2102         errorNameStr = String::ToString( "%s (%d)", object->getClassName(), object->getId() );
2103
2104      if ( !file )
2105         Con::errorf("PersistenceManager::removeObjectFromFile(): File was null trying to save %s", errorNameStr.c_str() );
2106      else            
2107         Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to open %s to save %s", file, errorNameStr.c_str() );            
2108
2109      // Reset everything
2110      clearAll();
2111
2112      return;
2113   }
2114
2115   ParsedObject* parsedObject = findParsedObject(object);
2116
2117   if (!parsedObject)
2118   {
2119      const char *name = object->getName();
2120      if (name)
2121      {
2122         Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to find %s %s (%d) in %s",
2123            object->getClassName(), name, object->getId(), file);
2124      }
2125      else
2126      {
2127         Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to find %s (%d) in %s",
2128            object->getClassName(), object->getId(), file);
2129      }
2130
2131      // Reset everything
2132      clearAll();
2133
2134      return;
2135   }
2136
2137   removeParsedObject(parsedObject);
2138
2139   for (U32 i = 0; i < mObjectBuffer.size(); i++)
2140   {
2141      ParsedObject* removeObj = mObjectBuffer[i];
2142
2143      if (removeObj == parsedObject)
2144      {
2145         mObjectBuffer.erase(i);
2146         break;
2147      }
2148   }
2149
2150   deleteObject(parsedObject);
2151
2152   // Save out the file
2153   if (mCurrentFile)
2154      saveDirtyFile();
2155
2156   // Reset everything
2157   clearAll();
2158}
2159
2160void PersistenceManager::deleteObjectsFromFile(const char* fileName)
2161{
2162   if ( mCurrentFile )
2163   {
2164      Con::errorf( "PersistenceManager::deleteObjectsFromFile(): Cannot process while file while another is currently open." );
2165      return;
2166   }
2167
2168   // Expand Script File.
2169   char buffer[1024];
2170   Con::expandScriptFilename( buffer, 1024, fileName );
2171
2172   // Parse File.
2173   if ( !parseFile( StringTable->insert(buffer) ) )
2174   {
2175      // Invalid.
2176      return;
2177   }
2178
2179   // Iterate over the objects.
2180   for ( Vector<ParsedObject*>::iterator itr = mObjectBuffer.begin(); itr != mObjectBuffer.end(); itr++ )
2181   {
2182      SimObject *object;
2183      if ( Sim::findObject( ( *itr )->name, object ) )
2184      {
2185         // Delete the Object.
2186         object->deleteObject();
2187      }
2188   }
2189
2190   // Clear.
2191   clearAll();
2192}
2193
2194DefineEngineMethod( PersistenceManager, deleteObjectsFromFile, void, ( const char * fileName ), , "( fileName )"
2195              "Delete all of the objects that are created from the given file." )
2196{
2197   // Delete Objects.
2198   object->deleteObjectsFromFile( fileName );
2199}
2200
2201DefineEngineMethod( PersistenceManager, setDirty, void,  ( const char * objName, const char * fileName ), (""), "(SimObject object, [filename])"
2202              "Mark an existing SimObject as dirty (will be written out when saveDirty() is called).")
2203{
2204   SimObject *dirtyObject = NULL;
2205   if (String::compare(objName,"") != 0)
2206   {
2207      if (!Sim::findObject(objName, dirtyObject))
2208      {
2209         Con::printf("PersistenceManager::setDirty(): Invalid SimObject: %s", objName);
2210         return;
2211      }
2212   }
2213   
2214   // Prevent ourselves from shooting us in the foot.
2215   if( dirtyObject == Sim::getRootGroup() )
2216   {
2217      Con::errorf( "PersistenceManager::setDirty(): Cannot save RootGroup" );
2218      return;
2219   }
2220
2221   if (dirtyObject)
2222   {
2223      if (String::compare( fileName,"")!=0)
2224         object->setDirty(dirtyObject, fileName);
2225      else
2226         object->setDirty(dirtyObject);
2227   }
2228}
2229
2230DefineEngineMethod( PersistenceManager, removeDirty, void, ( const char * objName ), , "(SimObject object)"
2231              "Remove a SimObject from the dirty list.")
2232{
2233   SimObject *dirtyObject = NULL;
2234   if (String::compare(  objName,"")!=0)
2235   {
2236      if (!Sim::findObject(objName, dirtyObject))
2237      {
2238         Con::printf("PersistenceManager::removeDirty(): Invalid SimObject: %s", objName);
2239         return;
2240      }
2241   }
2242
2243   if (dirtyObject)
2244      object->removeDirty(dirtyObject);
2245}
2246
2247DefineEngineMethod( PersistenceManager, isDirty, bool, ( const char * objName ), , "(SimObject object)"
2248              "Returns true if the SimObject is on the dirty list.")
2249{
2250   SimObject *dirtyObject = NULL;
2251   if (String::compare ( objName,"")!=0)
2252   {
2253      if (!Sim::findObject(objName, dirtyObject))
2254      {
2255         Con::printf("PersistenceManager::isDirty(): Invalid SimObject: %s", objName);
2256         return false;
2257      }
2258   }
2259
2260   if (dirtyObject)
2261      return object->isDirty(dirtyObject);
2262
2263   return false;
2264}
2265
2266DefineEngineMethod( PersistenceManager, hasDirty, bool, (), , "()"
2267              "Returns true if the manager has dirty objects to save." )
2268{
2269   return object->hasDirty();
2270}
2271
2272DefineEngineMethod( PersistenceManager, getDirtyObjectCount, S32, (), , "()"
2273              "Returns the number of dirty objects." )
2274{
2275   return object->getDirtyList().size();
2276}
2277
2278DefineEngineMethod( PersistenceManager, getDirtyObject, S32, (S32 index), , "( index )"
2279              "Returns the ith dirty object." )
2280{
2281   if ( index < 0 || index >= object->getDirtyList().size() )
2282   {
2283      Con::warnf( "PersistenceManager::getDirtyObject() - Index (%s) out of range.", index );
2284      return 0;
2285   }
2286
2287   // Fetch Object.
2288   const PersistenceManager::DirtyObject& dirtyObject = object->getDirtyList()[index];
2289
2290   // Return Id.
2291   return ( dirtyObject.getObject() ) ? dirtyObject.getObject()->getId() : 0;
2292}
2293
2294DefineEngineMethod( PersistenceManager, listDirty, void, (), , "()"
2295              "Prints the dirty list to the console.")
2296{
2297   const PersistenceManager::DirtyList dirtyList = object->getDirtyList();
2298
2299   for(U32 i = 0; i < dirtyList.size(); i++)
2300   {
2301      const PersistenceManager::DirtyObject& dirtyObject = dirtyList[i];
2302
2303      if (dirtyObject.isNull())
2304         continue;
2305
2306      SimObject *obj = dirtyObject.getObject();
2307      bool isSet = dynamic_cast<SimSet *>(obj) != 0;
2308      const char *name = obj->getName();
2309      if (name)
2310      {
2311         Con::printf("   %d,\"%s\": %s %s %s", obj->getId(), name,
2312         obj->getClassName(), dirtyObject.fileName, isSet ? "(g)":"");
2313      }
2314      else
2315      {
2316         Con::printf("   %d: %s %s, %s", obj->getId(), obj->getClassName(),
2317         dirtyObject.fileName, isSet ? "(g)" : "");
2318      }
2319   }
2320}
2321
2322DefineEngineMethod( PersistenceManager, saveDirty, bool, (), , "()"
2323              "Saves all of the SimObject's on the dirty list to their respective files.")
2324{
2325   return object->saveDirty();
2326}
2327
2328DefineEngineMethod( PersistenceManager, saveDirtyObject, bool, (const char * objName), , "(SimObject object)"
2329              "Save a dirty SimObject to it's file.")
2330{
2331   SimObject *dirtyObject = NULL;
2332   if (String::compare (  objName, "")!=0)
2333   {
2334      if (!Sim::findObject(objName, dirtyObject))
2335      {
2336         Con::printf("%s(): Invalid SimObject: %s", object->getName(), objName);
2337         return false;
2338      }
2339   }
2340
2341   if (dirtyObject)
2342      return object->saveDirtyObject(dirtyObject);
2343   return false;
2344}
2345
2346DefineEngineMethod( PersistenceManager, clearAll, void, (), , "()"
2347              "Clears all the tracked objects without saving them." )
2348{
2349   object->clearAll();
2350}
2351
2352DefineEngineMethod( PersistenceManager, removeObjectFromFile, void, (const char * objName, const char * filename),("") , "(SimObject object, [filename])"
2353              "Remove an existing SimObject from a file (can optionally specify a different file than \
2354               the one it was created in.")
2355{
2356   SimObject *dirtyObject = NULL;
2357   if (String::compare ( objName , "")!=0)
2358   {
2359      if (!Sim::findObject(objName, dirtyObject))
2360      {
2361         Con::printf("PersistenceManager::removeObjectFromFile(): Invalid SimObject: %s", objName);
2362         return;
2363      }
2364   }
2365
2366   if (dirtyObject)
2367   {
2368      if (String::compare( filename,"")!=0)
2369         object->removeObjectFromFile(dirtyObject, filename);
2370      else
2371         object->removeObjectFromFile(dirtyObject);
2372   }
2373}
2374
2375DefineEngineMethod( PersistenceManager, removeField, void, (const char * objName, const char * fieldName), , "(SimObject object, string fieldName)"
2376              "Remove a specific field from an object declaration.")
2377{
2378   SimObject *dirtyObject = NULL;
2379   if (String::compare(objName,"")!=0)
2380   {
2381      if (!Sim::findObject(objName, dirtyObject))
2382      {
2383         Con::printf("PersistenceManager::removeField(): Invalid SimObject: %s", objName);
2384         return;
2385      }
2386   }
2387
2388   if (dirtyObject)
2389   {
2390      if (String::compare(fieldName,"") != 0)
2391         object->addRemoveField(dirtyObject, fieldName);
2392   }
2393}
2394