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