Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/core7/src/libraries/core/Loader.cc @ 10403

Last change on this file since 10403 was 10392, checked in by landauf, 9 years ago

Loader is now a singleton instead of a static class. fixes issue with statically initialized ClassTreeMask (which again requires a BaseObject-Identifier)

  • Property svn:eol-style set to native
File size: 19.3 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Fabian 'x3n' Landau
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "Loader.h"
30
31#include <sstream>
32#include <tinyxml/ticpp.h>
33#include <boost/scoped_ptr.hpp>
34#include <boost/filesystem.hpp>
35#include <boost/filesystem/fstream.hpp>
36
37#include "util/Output.h"
38#include "util/Exception.h"
39#include "util/StringUtils.h"
40#include "BaseObject.h"
41#include "LuaState.h"
42#include "Namespace.h"
43#include "Resource.h"
44#include "XMLFile.h"
45#include "object/Iterator.h"
46#include "object/ObjectList.h"
47
48namespace orxonox
49{
50    Loader* Loader::singletonPtr_s = 0;
51
52    bool Loader::open(const XMLFile* file, const ClassTreeMask& mask, bool bVerbose)
53    {
54        this->add(file, mask);
55        return this->load(file, mask, bVerbose);
56    }
57
58    void Loader::close()
59    {
60        this->unload();
61        this->files_.clear();
62    }
63
64    void Loader::close(const XMLFile* file)
65    {
66        this->unload(file);
67        this->remove(file);
68    }
69
70    void Loader::add(const XMLFile* file, const ClassTreeMask& mask)
71    {
72        if (!file)
73            return;
74        this->files_.insert(this->files_.end(), std::pair<const XMLFile*, ClassTreeMask>(file, mask));
75    }
76
77    void Loader::remove(const XMLFile* file)
78    {
79        if (!file)
80            return;
81        for (std::vector<std::pair<const XMLFile*, ClassTreeMask> >::iterator it = this->files_.begin(); it != this->files_.end(); ++it)
82        {
83            if (it->first == file)
84            {
85                this->files_.erase(it);
86                break;
87            }
88        }
89    }
90
91    /**
92    @brief
93        Loads all opened files, while conforming to the restrictions given by the input ClassTreeMask.
94    @param mask
95        A ClassTreeMask, which defines which types of classes are loaded and which aren't.
96    @param bVerbose
97        Whether the loader is verbose (prints its progress in a low output level) or not.
98    @return
99        Returns true if successful.
100    */
101    bool Loader::load(const ClassTreeMask& mask, bool bVerbose)
102    {
103        bool success = true;
104        for (std::vector<std::pair<const XMLFile*, ClassTreeMask> >::iterator it = this->files_.begin(); it != this->files_.end(); ++it)
105            if (!this->load(it->first, it->second * mask, bVerbose))
106                success = false;
107
108        return success;
109    }
110
111    void Loader::unload(const ClassTreeMask& mask)
112    {
113        for (ObjectList<BaseObject>::iterator it = ObjectList<BaseObject>::begin(); it != ObjectList<BaseObject>::end(); )
114        {
115            if (mask.isIncluded(it->getIdentifier()))
116                (it++)->destroy();
117            else
118                ++it;
119        }
120    }
121
122    /**
123    @brief
124        Reloads all opened files, while conforming to the restrictions given by the input ClassTreeMask.
125    @param mask
126        A ClassTreeMask, which defines which types of classes are reloaded and which aren't.
127    @param bVerbose
128        Whether the loader is verbose (prints its progress in a low output level) or not.
129    @return
130        Returns true if successful.
131    */
132    bool Loader::reload(const ClassTreeMask& mask, bool bVerbose)
133    {
134        this->unload(mask);
135        return this->load(mask, bVerbose);
136    }
137
138    /**
139    @brief
140        Loads the input file, while conforming to the restrictions given by the input ClassTreeMask.
141    @param file
142        The file to be loaded.
143    @param mask
144        A ClassTreeMask, which defines which types of classes are loaded and which aren't.
145    @param bVerbose
146        Whether the loader is verbose (prints its progress in a low output level) or not.
147    @param bRemoveLuaTags
148        If true lua tags are just ignored and removed. The default is false.
149    @return
150        Returns true if successful.
151    */
152    bool Loader::load(const XMLFile* file, const ClassTreeMask& mask, bool bVerbose, bool bRemoveLuaTags)
153    {
154        if (!file)
155            return false;
156
157        this->currentMask_ = file->getMask() * mask;
158
159        std::string xmlInput;
160
161        shared_ptr<std::vector<std::vector<std::pair<std::string, size_t> > > > lineTrace(new std::vector<std::vector<std::pair<std::string, size_t> > >());
162        lineTrace->reserve(1000); //arbitrary number
163
164
165        if (file->getLuaSupport() && !bRemoveLuaTags)
166        {
167            // Use the LuaState to replace the XML tags (calls our function)
168            scoped_ptr<LuaState> luaState(new LuaState());
169            luaState->setTraceMap(lineTrace);
170            luaState->setIncludeParser(&Loader::replaceLuaTags);
171            luaState->includeFile(file->getFilename());
172            xmlInput = luaState->getOutput().str();
173        }
174        else
175        {
176            shared_ptr<ResourceInfo> info = Resource::getInfo(file->getFilename());
177            if (info == NULL)
178            {
179                orxout(user_error, context::loader) << "Could not find XML file '" << file->getFilename() << "'." << endl;
180                return false;
181            }
182            xmlInput = Resource::open(file->getFilename())->getAsString();
183
184            if (bRemoveLuaTags)
185            {
186                // Remove all Lua code.
187                // Note: we only need this to speed up parsing of level files at the
188                // start of the program.
189                // Assumption: the LevelInfo tag does not use Lua scripting
190                xmlInput = Loader::removeLuaTags(xmlInput);
191            }
192        }
193
194        try
195        {
196            if(bVerbose)
197            {
198                orxout(user_info) << "Start loading " << file->getFilename() << "..." << endl;
199                orxout(internal_info, context::loader) << "Mask: " << this->currentMask_ << endl;
200            }
201            else
202            {
203                orxout(verbose, context::loader) << "Start loading " << file->getFilename() << "..." << endl;
204                orxout(verbose_more, context::loader) << "Mask: " << this->currentMask_ << endl;
205            }
206
207            ticpp::Document xmlfile(file->getFilename());
208            xmlfile.Parse(xmlInput, true);
209
210            ticpp::Element rootElement;
211            rootElement.SetAttribute("name", "root");
212            rootElement.SetAttribute("bAutogenerated", true);
213
214            for (ticpp::Iterator<ticpp::Element> child = xmlfile.FirstChildElement(false); child != child.end(); child++)
215                rootElement.InsertEndChild(*child);
216
217            orxout(verbose, context::loader) << "  creating root-namespace..." << endl;
218            Namespace* rootNamespace = new Namespace(Context::getRootContext());
219            rootNamespace->setLoaderIndentation("    ");
220            rootNamespace->setFile(file);
221            rootNamespace->setNamespace(rootNamespace);
222            rootNamespace->setRoot(true);
223            rootNamespace->XMLPort(rootElement, XMLPort::LoadObject);
224
225            if(bVerbose)
226                orxout(user_info) << "Finished loading " << file->getFilename() << '.' << endl;
227            else
228                orxout(verbose, context::loader) << "Finished loading " << file->getFilename() << '.' << endl;
229
230            orxout(verbose, context::loader) << "Namespace-tree:" << '\n' << rootNamespace->toString("  ") << endl;
231
232            return true;
233        }
234        catch (ticpp::Exception& ex)
235        {
236            orxout(user_error, context::loader) << endl;
237            orxout(user_error, context::loader) << "An XML-error occurred in Loader.cc while loading " << file->getFilename() << ':' << endl;
238            OutputLevel ticpplevel = user_error;
239            if (lineTrace->size() > 0)
240            {
241                ticpplevel = internal_error;
242                //Extract the line number from the exception
243                std::string tempstring(ex.what());
244                std::string::size_type pos = tempstring.find("\nLine: ");
245                if (pos != std::string::npos)
246                {
247                    std::istringstream istr(tempstring.substr(pos + 7));
248                    size_t line;
249                    istr >> line;
250                    if (line <= lineTrace->size())
251                    {
252                        std::vector<std::pair<std::string, size_t> > linesources = lineTrace->at(line - 1);
253                        std::ostringstream message;
254                        message << "Possible sources of error:" << endl;
255                        for (std::vector<std::pair<std::string, size_t> >::iterator it = linesources.begin(); it != linesources.end(); ++it)
256                        {
257                            message << it->first << ", Line " << it->second << endl;
258                        }
259                        orxout(user_error, context::loader) << message.str() << endl;
260                    }
261                }
262            }
263            orxout(ticpplevel, context::loader) << ex.what() << endl;
264            orxout(user_error, context::loader) << "Loading aborted." << endl;
265        }
266        catch (Exception& ex)
267        {
268            orxout(user_error, context::loader) << endl;
269            orxout(user_error, context::loader) << "A loading-error occurred in Loader.cc while loading " << file->getFilename() << ':' << endl;
270            orxout(user_error, context::loader) << ex.what() << endl;
271            orxout(user_error, context::loader) << "Loading aborted." << endl;
272        }
273        catch (...)
274        {
275            orxout(user_error, context::loader) << endl;
276            orxout(user_error, context::loader) << "An error occurred in Loader.cc while loading " << file->getFilename() << ':' << endl;
277            orxout(user_error, context::loader) << Exception::handleMessage() << endl;
278            orxout(user_error, context::loader) << "Loading aborted." << endl;
279        }
280        //The Tardis' version of boost is too old...
281#if BOOST_VERSION >= 104600
282        boost::filesystem::path temppath = boost::filesystem::temp_directory_path() / "orxonoxml.xml";
283        //Need binary mode, because xmlInput already has \r\n for windows
284        boost::filesystem::ofstream outfile(temppath, std::ios_base::binary | std::ios_base::out);
285        outfile << xmlInput;
286        outfile.flush();
287        outfile.close();
288        orxout(internal_error, context::loader) << "The complete xml file has been saved to " << temppath << endl;
289#endif
290        return false;
291    }
292
293    void Loader::unload(const XMLFile* file, const ClassTreeMask& mask)
294    {
295        if (!file)
296            return;
297        for (ObjectList<BaseObject>::iterator it = ObjectList<BaseObject>::begin(); it; )
298        {
299            if ((it->getFile() == file) && mask.isIncluded(it->getIdentifier()))
300                (it++)->destroy();
301            else
302                ++it;
303        }
304    }
305
306    /**
307    @brief
308        Reloads the input file, while conforming to the restrictions given by the input ClassTreeMask.
309    @param file
310        The file to be reloaded.
311    @param mask
312        A ClassTreeMask, which defines which types of classes are reloaded and which aren't.
313    @param bVerbose
314        Whether the loader is verbose (prints its progress in a low output level) or not.
315    @return
316        Returns true if successful.
317    */
318    bool Loader::reload(const XMLFile* file, const ClassTreeMask& mask, bool bVerbose)
319    {
320        this->unload(file, mask);
321        return this->load(file, mask, bVerbose);
322    }
323
324    bool Loader::getLuaTags(const std::string& text, std::map<size_t, bool>& luaTags)
325    {
326        // fill map with all Lua tags
327        {
328            size_t pos = 0;
329            while ((pos = text.find("<?lua", pos)) != std::string::npos)
330                luaTags[pos++] = true;
331        }
332        {
333            size_t pos = 0;
334            while ((pos = text.find("?>", pos)) != std::string::npos)
335                luaTags[pos++] = false;
336        }
337
338        // erase all tags from the map that are between two quotes
339        // that means occurrences like "..<?lua.." and "..?>.." would be deleted
340        // however occurrences of lua tags within quotas are retained: ".. <?lua ... ?> .. "
341        {
342            std::map<size_t, bool>::iterator it = luaTags.begin();
343            bool bBetweenQuotes = false;
344            size_t pos = 0;
345            while ((pos = getNextQuote(text, pos)) != std::string::npos)
346            {
347                while ((it != luaTags.end()) && (it->first < pos))
348                {
349                    if (bBetweenQuotes)
350                    {
351                        std::map<size_t, bool>::iterator it2 = it;
352                        it2++;
353                        if (it->second && !(it2->second) && it2->first < pos)
354                            std::advance(it, 2);
355                        else
356                            luaTags.erase(it++);
357                    }
358                    else
359                        ++it;
360                }
361                bBetweenQuotes = !bBetweenQuotes;
362                pos++;
363            }
364        }
365
366        // check whether on every opening <?lua tag a closing ?> tag follows
367        {
368            bool expectedValue = true;
369            for (std::map<size_t, bool>::iterator it = luaTags.begin(); it != luaTags.end(); ++it)
370            {
371                if (it->second == expectedValue)
372                    expectedValue = !expectedValue;
373                else
374                {
375                    expectedValue = false;
376                    break;
377                }
378            }
379            if (!expectedValue)
380            {
381                orxout(internal_error, context::loader) << "Error parsing file: lua tags not matching" << endl;
382                // TODO: error handling
383                return false;
384            }
385        }
386
387        return true;
388    }
389
390    std::string Loader::replaceLuaTags(const std::string& text)
391    {
392        // create a map with all lua tags
393        std::map<size_t, bool> luaTags;
394        if (!getLuaTags(text, luaTags))
395            return "";
396
397        // Use a stringstream object to speed up the parsing
398        std::ostringstream output;
399
400        // cut the original string into pieces and put them together with print() instead of lua tags
401        {
402            std::map<size_t, bool>::iterator it = luaTags.begin();
403            bool bInPrintFunction = true;
404            size_t start = 0;
405            size_t end = 0;
406
407            do
408            {
409                if (it != luaTags.end())
410                    end = (it++)->first;
411                else
412                    end = std::string::npos;
413
414                unsigned int equalSignCounter = 0;
415
416                if (bInPrintFunction)
417                {
418                    // count ['='[ and ]'='] and replace tags with print([[ and ]])
419                    const std::string& temp = text.substr(start, end - start);
420                    {
421                    size_t pos = 0;
422                    while ((pos = temp.find('[', pos)) != std::string::npos)
423                    {
424                        unsigned int tempCounter = 1;
425                        size_t tempPos = pos++;
426                        while (temp[++tempPos] == '=')
427                        {
428                            tempCounter++;
429                        }
430                        if (temp[tempPos] != '[')
431                        {
432                            tempCounter = 0;
433                        }
434                        else if (tempCounter == 0)
435                        {
436                            tempCounter = 1;
437                        }
438                        if (tempCounter > equalSignCounter)
439                            equalSignCounter = tempCounter;
440                        }
441                    }
442                    {
443                        size_t pos = 0;
444                        while ((pos = temp.find(']', pos)) != std::string::npos)
445                        {
446                            unsigned int tempCounter = 1;
447                            size_t tempPos = pos++;
448                            while (temp[++tempPos] == '=')
449                            {
450                                tempCounter++;
451                            }
452                            if (temp[tempPos] != ']')
453                            {
454                                tempCounter = 0;
455                            }
456                            else if (tempCounter == 0)
457                            {
458                                tempCounter = 1;
459                            }
460                            if (tempCounter > equalSignCounter)
461                                equalSignCounter = tempCounter;
462                        }
463                    }
464                    std::string equalSigns;
465                    for (unsigned int i = 0; i < equalSignCounter; i++)
466                    {
467                        equalSigns += '=';
468                    }
469                    //A newline directly after square brackets is ignored. To make sure that the string is printed
470                    //exactly as it is, including newlines at the beginning, insert a space after the brackets.
471                    bool needsExtraSpace = false;
472                    if (temp.size() > 0 && (temp[0] == '\n' || temp[0] == '\r')) // begins with \n or \r (a line break)
473                        needsExtraSpace = true;
474                    output << "print([" + equalSigns + (needsExtraSpace ? "[ " : "[") + temp + ']' + equalSigns +"])";
475                    start = end + 5;
476                }
477                else
478                {
479                    output << text.substr(start, end - start);
480                    start = end + 2;
481                }
482
483                bInPrintFunction = !bInPrintFunction;
484            }
485            while (end != std::string::npos);
486        }
487
488        return output.str();
489    }
490
491    std::string Loader::removeLuaTags(const std::string& text)
492    {
493        // create a map with all lua tags
494        std::map<size_t, bool> luaTags;
495        if (!getLuaTags(text, luaTags))
496            return "";
497
498        // Use a stringstream object to speed up the concatenation
499        std::ostringstream output;
500
501        // cut the original string into pieces and only write the non Lua parts
502        std::map<size_t, bool>::iterator it = luaTags.begin();
503        bool bLuaCode = false;
504        size_t start = 0;
505        size_t end = 0;
506
507        do
508        {
509            if (it != luaTags.end())
510                end = (it++)->first;
511            else
512                end = std::string::npos;
513
514            if (!bLuaCode)
515            {
516                output << text.substr(start, end - start);
517                start = end + 5;
518            }
519            else
520            {
521                //Preserve the amount of lines, otherwise the linenumber from the xml parse error is useless
522                std::string tempstring = text.substr(start, end - start);
523                output << std::string(std::count(tempstring.begin(), tempstring.end(), '\n'), '\n');
524                start = end + 2;
525            }
526
527            bLuaCode = !bLuaCode;
528        }
529        while (end != std::string::npos);
530
531        return output.str();
532    }
533}
Note: See TracBrowser for help on using the repository browser.