1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3 
4     SPDX-FileCopyrightText: 2003 Brian Thomas <thomas@mail630.gsfc.nasa.gov>
5     SPDX-FileCopyrightText: 2004-2021 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
6 */
7 
8 // own header
9 #include "codedocument.h"
10 
11 // local includes
12 #include "codegenerator.h"
13 #include "debug_utils.h"
14 #include "package.h"
15 #include "umldoc.h"
16 #include "uml.h"
17 
18 // qt includes
19 #include <QDateTime>
20 #include <QRegExp>
21 #include <QXmlStreamWriter>
22 
23 /**
24  * Constructor.
25  */
CodeDocument()26 CodeDocument::CodeDocument () : CodeGenObjectWithTextBlocks(this),
27     m_lastTagIndex(0), m_filename(QString()), m_fileExtension(QString()),
28     m_ID(QString()), m_pathName(QString()), m_package(0), m_writeOutCode(true)
29 {
30     setHeader(new CodeComment(this));
31     //  m_dialog = new CodeDocumentDialog();
32 }
33 
34 /**
35  * Destructor.
36  */
~CodeDocument()37 CodeDocument::~CodeDocument ()
38 {
39     // delete all the text blocks we have
40     while (!m_textblockVector.isEmpty()) {
41         delete m_textblockVector.takeFirst();
42     }
43     delete m_header;
44 }
45 
46 /**
47  * Set the complete value (name plus any extension) of m_filename.
48  * @param new_var   the new value of m_filename
49  */
setFileName(const QString & new_var)50 void CodeDocument::setFileName (const QString &new_var)
51 {
52     m_filename = new_var;
53 }
54 
55 /**
56  * Get the value of m_filename. This name is the "complete" filename,
57  * meaning that it contains both the file name plus any extension (e.g. "file.cpp").
58  * @return   the value of m_filename
59  */
getFileName() const60 QString CodeDocument::getFileName () const
61 {
62     return m_filename;
63 }
64 
65 /**
66  * Set the value of m_fileExtension.
67  * @param new_var   the new value of m_fileExtension
68  */
setFileExtension(const QString & new_var)69 void CodeDocument::setFileExtension (const QString &new_var)
70 {
71     m_fileExtension = new_var;
72     updateHeader(); // because we are using new heading file
73 }
74 
75 /**
76  * Get the value of m_fileExtension.
77  * @return   the value of m_fileExtension
78  */
getFileExtension() const79 QString CodeDocument::getFileExtension() const
80 {
81     return m_fileExtension;
82 }
83 
84 /**
85  * Set the value of m_package.
86  * @param new_var   the new value of m_package
87  */
setPackage(UMLPackage * new_var)88 void CodeDocument::setPackage (UMLPackage *new_var)
89 {
90     m_package = new_var;
91 }
92 
93 /**
94  * Get the value of the path to this code document.
95  * @return the value of m_pathName
96  */
getPath() const97 QString CodeDocument::getPath () const
98 {
99     QString path = getPackage();
100 
101     // Replace all white spaces with blanks
102     path = path.simplified();
103 
104     // Replace all blanks with underscore
105     path.replace(QRegExp(QLatin1String(" ")), QLatin1String("_"));
106 
107     // this allows multiple directory paths (ala Java, some other languages)
108     // in from the package specification
109     path.replace(QRegExp(QLatin1String("\\.")), QLatin1String("/"));
110     // Simple hack!.. but this is more or less language
111     // dependent and should probably be commented out.
112     // Still, as a general default it may be useful -b.t.
113     return path;
114 }
115 
116 /**
117  * Get the value of the package of this code document.
118  * @return   the value of m_pathName
119  */
getPackage() const120 QString CodeDocument::getPackage () const
121 {
122     if (m_package)
123         return m_package->name();
124     return QString();
125 }
126 
127 /**
128  * Set the value of m_ID.
129  * @param new_id   the new value of m_ID
130  */
setID(const QString & new_id)131 void CodeDocument::setID (const QString &new_id)
132 {
133     m_ID = new_id;
134 }
135 
136 /**
137  * Get the value of m_ID.
138  * @return   the value of m_ID
139  */
ID() const140 QString CodeDocument::ID () const
141 {
142     return m_ID;
143 }
144 
145 /**
146  * Set the value of m_writeOutCode.
147  * Whether or not to write out this code document and any codeblocks, etc that it
148  * owns.
149  * @param new_var   the new value of m_writeOutCode
150  */
setWriteOutCode(bool new_var)151 void CodeDocument::setWriteOutCode (bool new_var)
152 {
153     m_writeOutCode = new_var;
154 }
155 
156 /**
157  * Get the value of m_writeOutCode.
158  * Whether or not to write out this code document and any codeblocks, etc that it
159  * owns.
160  * @return   the value of m_writeOutCode
161  */
getWriteOutCode() const162 bool CodeDocument::getWriteOutCode () const
163 {
164     return m_writeOutCode;
165 }
166 
167 /**
168  * Set a Header comment object.
169  * @param comment   the comment for the header
170  */
setHeader(CodeComment * comment)171 void CodeDocument::setHeader (CodeComment * comment)
172 {
173     m_header = comment;
174 }
175 
176 /**
177  * Get the Header comment object.
178  * @return   the comment for the header
179  */
getHeader() const180 CodeComment * CodeDocument::getHeader () const
181 {
182     return m_header;
183 }
184 
185 /**
186  * Return a unique and currently unallocated text block tag for this document.
187  * @param prefix   the prefix to add
188  * @return         the just created unique tag
189  */
getUniqueTag(const QString & prefix)190 QString CodeDocument::getUniqueTag (const QString& prefix)
191 {
192     QString tag = prefix ;
193     if(tag.isEmpty())
194         tag += QLatin1String("tblock");
195 
196     tag = tag + QLatin1String("_0");
197     int number = m_lastTagIndex;
198     for (; findTextBlockByTag(tag, true); ++number) {
199         tag = prefix + QLatin1Char('_') + QString::number(number);
200     }
201     m_lastTagIndex = number;
202     return tag;
203 }
204 
205 /**
206  * Insert a new text block after the existing text block. Returns
207  * false if it cannot insert the textblock.
208  * @param newBlock        the text block to insert
209  * @param existingBlock   the place where to insert
210  * @param after           at the index of the existingBlock or after
211  * @return                the success status
212  */
insertTextBlock(TextBlock * newBlock,TextBlock * existingBlock,bool after)213 bool CodeDocument::insertTextBlock(TextBlock * newBlock, TextBlock * existingBlock, bool after)
214 {
215     if (!newBlock || !existingBlock)
216         return false;
217 
218     QString tag = existingBlock->getTag();
219     if (!findTextBlockByTag(tag, true))
220         return false;
221 
222     int index = m_textblockVector.indexOf(existingBlock);
223     if (index < 0)
224     {
225         // may be hiding in child hierarchical codeblock
226         foreach (TextBlock* tb, m_textblockVector)
227         {
228             HierarchicalCodeBlock * hb = dynamic_cast<HierarchicalCodeBlock*>(tb);
229             if (hb && hb->insertTextBlock(newBlock, existingBlock, after))
230                 return true; // found, and inserted, otherwise keep going
231         }
232         // ugh. where is the child block?
233         uWarning() << " Warning: couldnt insert text block (tag:" << newBlock->getTag()
234                    << "). Reference text block (tag:" << existingBlock->getTag() << ") not found.";
235         return false;
236     }
237 
238     // if we get here.. it was in this object so insert
239 
240     // check for tag FIRST
241     QString new_tag = newBlock->getTag();
242 
243     // assign a tag if one doesn't already exist
244     if (new_tag.isEmpty())
245     {
246         new_tag = getUniqueTag();
247         newBlock->setTag(new_tag);
248     }
249 
250     if (m_textBlockTagMap.contains(new_tag))
251         return false; // return false, we already have some object with this tag in the list
252     else
253         m_textBlockTagMap.insert(new_tag, newBlock);
254 
255     if (after)
256         index++;
257 
258     m_textblockVector.insert(index, newBlock);
259     return true;
260 }
261 
262 /**
263  * A little utility method which calls CodeGenerator::cleanName.
264  * @param name   the cleanable name
265  * @return       the cleaned name
266  */
cleanName(const QString & name)267 QString CodeDocument::cleanName (const QString &name)
268 {
269     return CodeGenerator::cleanName(name);
270 }
271 
272 /**
273  * Update the header text of this codedocument
274  * (text and status of the head comment).
275  */
updateHeader()276 void CodeDocument::updateHeader ()
277 {
278     //try to find a heading file (license, comments, etc) then extract its text
279     QString headingText = UMLApp::app()->commonPolicy()->getHeadingFile(getFileExtension());
280 
281     headingText.replace(QRegExp(QLatin1String("%filename%")), getFileName()+getFileExtension());
282     headingText.replace(QRegExp(QLatin1String("%filepath%")), getPath());
283     headingText.replace(QRegExp(QLatin1String("%time%")), QTime::currentTime().toString());
284     headingText.replace(QRegExp(QLatin1String("%date%")), QDate::currentDate().toString());
285 
286     getHeader()->setText(headingText);
287 
288     // update the write out status of the header
289     if (UMLApp::app()->commonPolicy()->getIncludeHeadings())
290         getHeader()->setWriteOutText(true);
291     else
292         getHeader()->setWriteOutText(false);
293 }
294 
295 /**
296  * Create the string representation of this object.
297  * @return   the created string
298  */
toString() const299 QString CodeDocument::toString () const
300 {
301     // IF the whole document is turned "Off" then don't bother
302     // checking individual code blocks, just send back empty string
303     if (!getWriteOutCode())
304         return QString();
305 
306     QString content = getHeader()->toString();
307 
308     // update the time/date
309 
310     // comments, import, package codeblocks go next
311     TextBlockList * items = getTextBlockList();
312     foreach (TextBlock* c, *items)
313     {
314         if (c->getWriteOutText()) {
315             QString str = c->toString();
316             if (!str.isEmpty())
317                 content.append(str);
318         }
319     }
320     return content;
321 }
322 
323 /**
324  * Cause this code document to synchronize to current generator policy.
325  */
synchronize()326 void CodeDocument::synchronize()
327 {
328     updateContent();
329 }
330 
331 /**
332  * Reset/clear our inventory of textblocks in this document.
333  * Need to overload method to be able to clear the childTextBlockMap.
334  */
resetTextBlocks()335 void CodeDocument::resetTextBlocks()
336 {
337     CodeGenObjectWithTextBlocks::resetTextBlocks();
338     m_childTextBlockTagMap.clear();
339 }
340 
341 /**
342  * Load params from the appropriate XMI element node.
343  * @param root   the starting point for loading
344  */
loadFromXMI1(QDomElement & root)345 void CodeDocument::loadFromXMI1 (QDomElement & root)
346 {
347     setAttributesFromNode(root);
348 }
349 
350 /**
351  * Set attributes of the node that represents this class
352  * in the XMI document.
353  */
setAttributesOnNode(QXmlStreamWriter & writer)354 void CodeDocument::setAttributesOnNode (QXmlStreamWriter& writer)
355 {
356     // superclass call
357     CodeGenObjectWithTextBlocks::setAttributesOnNode(writer);
358 
359     // now set local attributes/fields
360     writer.writeAttribute(QLatin1String("fileName"), getFileName());
361     writer.writeAttribute(QLatin1String("fileExt"), getFileExtension());
362     Uml::ID::Type pkgId = Uml::ID::None;
363     if (m_package)
364         pkgId = m_package->id();
365     writer.writeAttribute(QLatin1String("package"), Uml::ID::toString(pkgId));
366     writer.writeAttribute(QLatin1String("writeOutCode"), getWriteOutCode() ? QLatin1String("true")
367                                                                              : QLatin1String("false"));
368     writer.writeAttribute(QLatin1String("id"), ID());
369 
370     // set the a header
371     // which we will store in its own separate child node block
372     writer.writeStartElement(QLatin1String("header"));
373     getHeader()->saveToXMI1(writer); // comment
374     writer.writeEndElement();
375 
376     // doc codePolicy?
377     // FIX: store ONLY if different from the parent generator
378     // policy.. something which is not possible right now. -b.t.
379 }
380 
381 /**
382  * Set the class attributes of this object from
383  * the passed element node.
384  */
setAttributesFromNode(QDomElement & root)385 void CodeDocument::setAttributesFromNode (QDomElement & root)
386 {
387     // now set local attributes
388     setFileName(root.attribute(QLatin1String("fileName")));
389     setFileExtension(root.attribute(QLatin1String("fileExt")));
390     QString pkgStr = root.attribute(QLatin1String("package"));
391     if (!pkgStr.isEmpty() && pkgStr != QLatin1String("-1")) {
392         UMLDoc *umldoc = UMLApp::app()->document();
393         if (pkgStr.contains(QRegExp(QLatin1String("\\D")))) {
394             // suspecting pre-1.5.3 file format where the package name was
395             // saved instead of the package ID.
396             UMLObject *o = umldoc->findUMLObject(pkgStr);
397             m_package = o->asUMLPackage();
398         }
399         if (m_package == 0) {
400             UMLObject *o = umldoc->findObjectById(Uml::ID::fromString(pkgStr));
401             m_package = o->asUMLPackage();
402         }
403     }
404     const QString trueStr = QLatin1String("true");
405     const QString wrOutCode = root.attribute(QLatin1String("writeOutCode"), trueStr);
406     setWriteOutCode(wrOutCode == trueStr);
407     setID(root.attribute(QLatin1String("id")));
408 
409     // load comment now
410     // by looking for our particular child element
411     QDomNode node = root.firstChild();
412     QDomElement element = node.toElement();
413     while (!element.isNull()) {
414         QString tag = element.tagName();
415         if (tag == QLatin1String("header")) {
416             QDomNode cnode = element.firstChild();
417             QDomElement celem = cnode.toElement();
418             getHeader()->loadFromXMI1(celem);
419             break;
420         }
421         node = element.nextSibling();
422         element = node.toElement();
423     }
424 
425     // a rare case where the super-class load is AFTER local attributes
426     CodeGenObjectWithTextBlocks::setAttributesFromNode(root);
427 }
428 
429 /**
430  * Save the XMI representation of this object.
431  * @param doc   the xmi document
432  * @param root  the starting point to append
433  */
saveToXMI1(QXmlStreamWriter & writer)434 void CodeDocument::saveToXMI1(QXmlStreamWriter& writer)
435 {
436     writer.writeStartElement(QLatin1String("codedocument"));
437     setAttributesOnNode(writer);
438     writer.writeEndElement();
439 }
440 
441 /**
442  * Update the content of this code document.
443  * This is where you should lay out your code document structure of textblocks
444  * in the inheriting class, should it have any text in it.
445  * Vanilla code documents don't have much to do.. override this with a different
446  * version for your own documents.
447  */
updateContent()448 void CodeDocument::updateContent()
449 {
450     updateHeader(); // doing this insures time/date stamp is at the time of this call
451 }
452 
453 /**
454  * Create a new CodeBlock object belonging to this CodeDocument.
455  * @return      the just created CodeBlock
456  */
newCodeBlock()457 CodeBlock * CodeDocument::newCodeBlock ()
458 {
459     return new CodeBlock(this);
460 }
461 
462 /**
463  * Create a new CodeBlockWithComments object belonging to this CodeDocument.
464  * @return   the just created CodeBlockWithComments
465  */
newCodeBlockWithComments()466 CodeBlockWithComments * CodeDocument::newCodeBlockWithComments ()
467 {
468     return new CodeBlockWithComments(this);
469 }
470 
471 /**
472  * Create a new HierarchicalCodeBlock object belonging to this CodeDocument.
473  * @return   the just created HierarchicalCodeBlock
474  */
newHierarchicalCodeBlock()475 HierarchicalCodeBlock * CodeDocument::newHierarchicalCodeBlock ()
476 {
477     HierarchicalCodeBlock *hb = new HierarchicalCodeBlock(this);
478     //hb->update();
479     return hb;
480 }
481 
removeChildTagFromMap(const QString & tag)482 void CodeDocument::removeChildTagFromMap (const QString &tag)
483 {
484     m_childTextBlockTagMap.remove(tag);
485 }
486 
addChildTagToMap(const QString & tag,TextBlock * tb)487 void CodeDocument::addChildTagToMap (const QString &tag, TextBlock * tb)
488 {
489     m_childTextBlockTagMap.insert(tag, tb);
490 }
491 
492 /**
493  * Lookup a certain textblock by its tag value, returns NULL if it cannot
494  * find the TextBlock with such a tag. If descendIntoChildren is true, then
495  * any child hierarchical textblocks will also be searched for a match.
496  * @param tag                   the tag to look for
497  * @param descendIntoChildren   look down the hierarchy
498  * @return                      the found text block
499  */
findTextBlockByTag(const QString & tag,bool descendIntoChildren) const500 TextBlock * CodeDocument::findTextBlockByTag(const QString &tag, bool descendIntoChildren) const
501 {
502     //if we already know to which file this class was written/should be written, just return it.
503     if (m_textBlockTagMap.contains(tag))
504         return m_textBlockTagMap[tag];
505 
506     if (descendIntoChildren)
507         if (m_childTextBlockTagMap.contains(tag))
508             return m_childTextBlockTagMap[tag];
509 
510     return 0;
511 }
512 
513 /**
514  * Have to implement this for CodeObjectWithTextBlocks.
515  * Actually does not do anything for a vanilla code document.
516  */
findCodeClassFieldTextBlockByTag(const QString & tag)517 TextBlock * CodeDocument::findCodeClassFieldTextBlockByTag (const QString &tag)
518 {
519     uWarning() << "Called findCodeClassFieldMethodByTag(" << tag << ") for a regular CodeDocument";
520     return 0;
521 }
522 
operator <<(QDebug os,const CodeDocument & obj)523 QDebug operator<<(QDebug os, const CodeDocument& obj)
524 {
525     os.nospace() << "CodeDocument: id=" << obj.ID()
526        << ", file=" << obj.getFileName();  //:TODO: add all attributes
527     return os.space();
528 }
529