1 ///////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2010 by The Allacrost Project
3 //                         All Rights Reserved
4 //
5 // This code is licensed under the GNU GPL version 2. It is free software
6 // and you may modify it and/or redistribute it under the terms of this license.
7 // See http://www.gnu.org/copyleft/gpl.html for details.
8 ///////////////////////////////////////////////////////////////////////////////
9 
10 /** ****************************************************************************
11 *** \file    script_modify.h
12 *** \author  Daniel Steuernol - steu@allacrost.org,
13 ***          Tyler Olsen - roots@allacrost.org
14 *** \brief   Header file for the ModifyScriptDescriptor class.
15 *** ***************************************************************************/
16 
17 #ifndef __SCRIPT_MODIFY_HEADER__
18 #define __SCRIPT_MODIFY_HEADER__
19 
20 #include "utils.h"
21 #include "defs.h"
22 
23 #include "script.h"
24 #include "script_read.h"
25 
26 namespace hoa_script {
27 
28 /** ****************************************************************************
29 *** \brief Represents a Lua file opened with read, execute, and modify permissions.
30 ***
31 *** This class features all the functionality found in the ReadScriptDescriptor
32 *** class, plus the additional ability to modify existing Lua data and save it
33 *** back to the file. An object of this class should only be created if the user
34 *** intends to modify the Lua data in the file at some point (i.e. don't use
35 *** this class over the ReadScriptDescriptor simply because it has more
36 *** functionality available).
37 ***
38 *** In order to permanently (and irreversibly) change Lua data in the file, the
39 *** user must call the CommitChanges function after making one or several
40 *** ModifyData calls.
41 ***
42 *** \note This class and is features are still highly experimental and incomplete.
43 ***
44 *** \todo Add ability to modify tables and their data
45 *** ***************************************************************************/
46 class ModifyScriptDescriptor : public ReadScriptDescriptor {
47 	friend class ScriptEngine;
48 public:
ModifyScriptDescriptor()49 	ModifyScriptDescriptor()
50 		{}
51 
52 	~ModifyScriptDescriptor();
53 
54 	/** \name File Access Functions
55 	*** \note These are derived from ScriptDescriptor, refer to the comments for these
56 	*** methods in the header file for that class.
57 	**/
58 	//@{
59 	bool OpenFile(const std::string& file_name);
60 	bool OpenFile();
61 	void CloseFile();
62 	//@}
63 
64 	/** \name Data Modifier Functions
65 	*** \brief These functions allow the modification of primitive data types
66 	*** \param key The string or integer key of the variable to change
67 	*** \param value The value to change the variable to
68 	*** \note If the key could not be found in the active scope, no change will
69 	*** occur and an error message will be logged.
70 	**/
71 	//@{
ModifyBool(const std::string & key,bool value)72 	void ModifyBool(const std::string& key, bool value)
73 		{ _ModifyData(key, value); }
74 
ModifyBool(int32 key,bool value)75 	void ModifyBool(int32 key, bool value)
76 		{ _ModifyData(key, value); }
77 
ModifyInt(const std::string & key,int32 value)78 	void ModifyInt(const std::string& key, int32 value)
79 		{ _ModifyData(key, value); }
80 
ModifyInt(int32 key,int32 value)81 	void ModifyInt(int32 key, int32 value)
82 		{ _ModifyData(key, value); }
83 
ModifyUInt(const std::string & key,uint32 value)84 	void ModifyUInt(const std::string& key, uint32 value)
85 		{ _ModifyData(key, value); }
86 
ModifyUInt(int32 key,uint32 value)87 	void ModifyUInt(int32 key, uint32 value)
88 		{ _ModifyData(key, value); }
89 
ModifyFloat(const std::string & key,float value)90 	void ModifyFloat(const std::string& key, float value)
91 		{ _ModifyData(key, value); }
92 
ModifyFloat(int32 key,float value)93 	void ModifyFloat(int32 key, float value)
94 		{ _ModifyData(key, value); }
95 
ModifyString(const std::string & key,const std::string & value)96 	void ModifyString(const std::string& key, const std::string& value)
97 		{ _ModifyData(key, value); }
98 
ModifyString(int32 key,const std::string & value)99 	void ModifyString(int32 key, const std::string& value)
100 		{ _ModifyData(key, value); }
101 	//@}
102 
103 	/** \name Data Addition Functions
104 	*** \brief These functions allow the addition of new key/value pairs
105 	*** \param key The string or integer key of the variable to add
106 	*** \param value The value to set the new variable to
107 	*** \note If the key already exists in the active scope when these
108 	*** methods are called, the value of that key will be overwritten
109 	*** with the new value. There will be no indication that this type of
110 	*** overwrite operation took place, so be cautious.
111 	**/
112 	//@{
AddNewBool(const std::string & key,bool value)113 	void AddNewBool(const std::string& key, bool value)
114 		{ _AddNewData(key, value); }
115 
AddNewBool(int32 key,bool value)116 	void AddNewBool(int32 key, bool value)
117 		{ _AddNewData(key, value); }
118 
AddNewInt(const std::string & key,int32 value)119 	void AddNewInt(const std::string& key, int32 value)
120 		{ _AddNewData(key, value); }
121 
AddNewInt(int32 key,int32 value)122 	void AddNewInt(int32 key, int32 value)
123 		{ _AddNewData(key, value); }
124 
AddNewUInt(const std::string & key,uint32 value)125 	void AddNewUInt(const std::string& key, uint32 value)
126 		{ _AddNewData(key, value); }
127 
AddNewUInt(int32 key,uint32 value)128 	void AddNewUInt(int32 key, uint32 value)
129 		{ _AddNewData(key, value); }
130 
AddNewFloat(const std::string & key,float value)131 	void AddNewFloat(const std::string& key, float value)
132 		{ _AddNewData(key, value); }
133 
AddNewFloat(int32 key,float value)134 	void AddNewFloat(int32 key, float value)
135 		{ _AddNewData(key, value); }
136 
AddNewString(const std::string & key,const std::string & value)137 	void AddNewString(const std::string& key, const std::string& value)
138 		{ _AddNewData(key, value); }
139 
AddNewString(int32 key,const std::string & value)140 	void AddNewString(int32 key, const std::string& value)
141 		{ _AddNewData(key, value); }
142 	//@}
143 
144 	// TODO: Add methods to allow the creation of new tables
145 // 	void CreateNewTable(const std::string& key);
146 // 	void CreateNewTable(uint32 key);
147 // 	void EndNewTable();
148 
149 	/** \brief Commits all modified changes to the Lua file for permanent retention
150 	*** \param leave_closed If set to true this file will be left closed once the function finishes (re-opens fileby default)
151 	***
152 	*** This is a heavy-weight function because it has to write out the entire Lua state to a file,
153 	*** regardless of whether one piece of data was modified or one hundred were. Therefore, you
154 	*** should only call this function when all data modifications are done and you need to save the
155 	*** result back to the hard-disk. Because the file referenced by this class is modified, the file
156 	*** is closed and optionally re-opened before the function returns.
157 	***
158 	*** \todo Spawn off this function in a seperate thread?
159 	**/
160 	void CommitChanges(bool leave_closed = false);
161 
162 private:
163 	/** \brief A helper function to CommitChanges() that writes out the contents of a table to the file
164 	*** \param file A reference to the open file to write the table to
165 	*** \param table A reference to the valid luabind::object that contains the table's contents
166 	**/
167 	void _CommitTable(WriteScriptDescriptor& file, const luabind::object& table);
168 
169 	/** \brief Template functions that update the key, value pair for the most recently opened table, or in the global scope
170 	*** \param key The key name of the variable to be change
171 	*** \param value The new value to set the key
172 	**/
173 	//@{
174 	template <class T> void _ModifyData(const std::string& key, T value);
175 	template <class T> void _ModifyData(int32 key, T value);
176 	//@}
177 
178 	/** \brief Template functions which add a new key, value pair to the most recently opened scope
179 	*** \param key The key name of the variable to add
180 	*** \param value The value to set for the new variable
181 	**/
182 	//@{
183 	template <class T> void _AddNewData(const std::string& key, T value);
184 	template <class T> void _AddNewData(int32 key, T value);
185 	//@}
186 
187 	/** \brief vector<string> iterator used to iterate over the list of open tables
188 	 ** This is stored here because it needs to persist over multiple function calls.
189 	 **/
190 	std::vector<std::string>::iterator _open_tables_iterator;
191 }; // class ModifyScriptDescriptor
192 
193 //-----------------------------------------------------------------------------
194 // Template Function Definitions
195 //-----------------------------------------------------------------------------
196 
_ModifyData(const std::string & key,T value)197 template <class T> void ModifyScriptDescriptor::_ModifyData(const std::string& key, T value) {
198 	// A pointer to the table where the object is contained
199 	luabind::object* table = NULL;
200 	// check to see if key is a table
201 	std::string search_key = key;
202 	int32 period = key.find('.');
203 	std::string tablename;
204 	std::vector<std::string> subkeys;
205 	if (period != static_cast<int32>(std::string::npos)) {
206 		// This key is a table with sub-keys
207 		tablename = key.substr(0, period);
208 		this->OpenTable(tablename);
209 		int last = period;
210 		while ((period = key.find('.', period + 1)) != static_cast<int32>(std::string::npos)) {
211 			// push all subkeys into this table
212 			subkeys.push_back(key.substr(last + 1, period));
213 			this->OpenTable(key.substr(last+1, period));
214 			last = period;
215 		}
216 		search_key = key.substr(last + 1);
217 	}
218 
219 	if (_open_tables.empty() == true) // Retrieve the globals table
220 		table = new luabind::object(luabind::from_stack(_lstack, LUA_GLOBALSINDEX));
221 	else // Retrieve the most recently opened table from the top of the stack
222 		table = new luabind::object(luabind::from_stack(_lstack, private_script::STACK_TOP));
223 
224 	if (luabind::type(*table) != LUA_TTABLE) {
225 		_error_messages << "* _ModifyData() failed because it could not construct the table "
226 			<< "where the data resided: " << key << std::endl;
227 		delete(table);
228 		return;
229 	}
230 
231 	for (luabind::iterator i(*table); i != private_script::TABLE_END; ++i) {
232 		// Check to see if global value exists
233 		try {
234 			if (luabind::object_cast<std::string>(i.key()) == search_key) {
235 				*i = value;
236 				delete(table);
237 				// close open tables
238 				this->CloseTable();
239 				return;
240 			}
241 		}
242 		catch (...)  {
243 			// A cast failure does not indicate an error, simply continue on with the next key
244 		}
245 	}
246 
247 	// If the code reaches this far, then the variable could not be found
248 	_error_messages << "* _ModifyData() failed because in the active scope, it did not find the "
249 		<< "table key: " << key << std::endl;
250 	delete(table);
251 	this->CloseTable();
252 } // template <class T> void ModifyScriptDescriptor::_ModifyData(const std::string& key, T value) {
253 
254 
255 
_ModifyData(int32 key,T value)256 template <class T> void ModifyScriptDescriptor::_ModifyData(int32 key, T value) {
257 	if (_open_tables.empty() == false) {
258 		_error_messages << "* _ModifyData() failed because there were no open tables when the "
259 			<< "function was invoked for key: " << key << std::endl;
260 		return;
261 	}
262 
263 	luabind::object table(luabind::from_stack(_lstack, private_script::STACK_TOP));
264 
265 	if (luabind::type(table) != LUA_TTABLE) {
266 		_error_messages << "* _ModifyData() failed because the top of the stack was not a table "
267 			<< "when trying to modify the data named: " << key << std::endl;
268 		return;
269 	}
270 
271 	for (luabind::iterator i(table); i != private_script::TABLE_END; ++i) {
272 		// Check to see if global value exists
273 		try {
274 			if (luabind::object_cast<int32>(i.key()) == key) {
275 				*i = value;
276 				delete(table);
277 				return;
278 			}
279 		}
280 		catch (...)  {
281 			// A cast failure does not indicate an error, simply continue on with the next key
282 		}
283 	}
284 
285 	// If the code reaches this far, then the variable could not be found
286 	_error_messages << "* _ModifyData() failed because in the active scope, it did not find the "
287 		<< "table key: " << key << std::endl;
288 	delete(table);
289 } // template <class T> void ModifyScriptDescriptor::_ModifyData(int32 key, T value) {
290 
291 
292 
_AddNewData(const std::string & key,T value)293 template <class T> void ModifyScriptDescriptor::_AddNewData(const std::string& key, T value) {
294 	// A pointer to the table where the object is contained
295 	luabind::object* table = NULL;
296 
297 	if (_open_tables.empty() == false) // Retrieve the globals table
298 		table = new luabind::object(luabind::from_stack(_lstack, LUA_GLOBALSINDEX));
299 	else // Retrieve the most recently opened table from the top of the stack
300 		table = new luabind::object(luabind::from_stack(_lstack, private_script::STACK_TOP));
301 
302 	if (luabind::type(*table) != LUA_TTABLE) {
303 		_error_messages << "* _AddNewData() failed because the top of the stack was not a table "
304 			<< "when trying to add the new data: " << key << std::endl;
305 		return;
306 	}
307 
308 	// NOTE: If the key already exists in the table, its value will be overwritten here
309 	luabind::settable(*table, key, value);
310 } // template <class T> void ModifyScriptDescriptor::_AddNewData(const std::string& key, T value)
311 
312 
313 
_AddNewData(int32 key,T value)314 template <class T> void ModifyScriptDescriptor::_AddNewData(int32 key, T value) {
315 	if (_open_tables.empty() == false) {
316 		_error_messages << "* _AddNewData() failed because there were no open tables when the "
317 			<< "function was invoked for key: " << key << std::endl;
318 		return;
319 	}
320 
321 	// A pointer to the table where the object is contained
322 	luabind::object table(luabind::from_stack(_lstack, private_script::STACK_TOP));
323 
324 	if (luabind::type(table) != LUA_TTABLE) {
325 		_error_messages << "* _AddNewData() failed because the top of the stack was not a table "
326 			<< "when trying to add the new data: " << key << std::endl;
327 		return;
328 	}
329 
330 	// NOTE: If the key already exists in the table, its value will be overwritten here
331 	luabind::settable(table, key, value);
332 } // template <class T> void ModifyScriptDescriptor::_AddNewData(int32 key, T value)
333 
334 } // namespace hoa_script
335 
336 #endif // __SCRIPT_MODIFY_HEADER
337