1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2017-2020 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5 
6 // own header
7 #include "diagram_utils.h"
8 
9 // app includes
10 #include "associationwidget.h"
11 #include "association.h"
12 #include "debug_utils.h"
13 #include "import_utils.h"
14 #include "messagewidget.h"
15 #include "object_factory.h"
16 #include "objectwidget.h"
17 #include "uml.h"
18 #include "umldoc.h"
19 #include "umlview.h"
20 #include "umlobject.h"
21 #include "umlscene.h"
22 #include "widget_factory.h"
23 
24 //// qt includes
25 #include <QListWidget>
26 #include <QMap>
27 #include <QMimeData>
28 #include <QRegExp>
29 
30 DEBUG_REGISTER_DISABLED(Diagram_Utils)
31 #undef DBG_SRC
32 #define DBG_SRC QLatin1String("Diagram_Utils")
33 
34 namespace Diagram_Utils {
35 
36 /**
37  * Detect sequence line format
38  * @param lines
39  * @return
40  */
detectSequenceLineFormat(const QStringList & lines)41 SequenceLineFormat detectSequenceLineFormat(const QStringList &lines)
42 {
43     QStringList l = lines;
44     while(l.size() > 0) {
45         QStringList cols = l.takeFirst().split(QRegExp(QLatin1String("\\s+")),QString::SkipEmptyParts);
46         if (cols.size() < 1)
47             continue;
48 
49         if (cols[0] == QLatin1String("#")) {
50             continue;
51         }
52         /*
53          * #0  0x000000000050d0b0 in Import_Utils::importStackTrace(QString const&, UMLScene*) (fileName=..., scene=scene@entry=0x12bd0f0)
54          */
55         if (cols.size() > 2 && cols[0].startsWith(QLatin1String("#")))
56             return GDB;
57         /*
58          *  6	Driver::ParseHelper::ParseHelper	driver.cpp	299	0x634c44
59          */
60         else if (cols[cols.size()-1].startsWith(QLatin1String("0x")))
61             return QtCreatorGDB;
62         /*
63          * FloatingTextWidget::setPreText
64          */
65         else if (cols[cols.size()-1].contains(QLatin1String("::")))
66             return Simple;
67         else
68             return Invalid;
69     }
70     return Invalid;
71 }
72 
73 /**
74  * Helper function to parse sequence line. The supported formats are:
75  *
76  * @param s string to parse
77  * @param sequence return of sequence number
78  * @param package return of package
79  * @param method return of method
80  * @return true line could be parsed and return variable has been filled
81  * @return false line could not be parsed, no return variable has been filled
82  */
parseSequenceLine(const QString & s,QString & sequence,QString & package,QString & method,QString & error)83 bool parseSequenceLine(const QString &s, QString &sequence, QString &package, QString &method, QString &error)
84 {
85     QString identifier;
86     QString module;
87     QStringList cols = s.split(QRegExp(QLatin1String("\\s+")),QString::SkipEmptyParts);
88     if (cols.size() < 1) {
89         error = QLatin1String("could not parse");
90         return false;
91     }
92     // skip comments
93     if (cols[0] == QLatin1String("#")) {
94         return false;
95     }
96 
97     /*  gdb
98      * #0  0x000000000050d0b0 in Import_Utils::importStackTrace(QString const&, UMLScene*) (fileName=..., scene=scene@entry=0x12bd0f0)
99      * #25 0x00007fffefe1670c in g_main_context_iteration () from /usr/lib64/libglib-2.0.so.0
100      * #0  Import_Utils::importStackTrace (fileName=..., scene=scene@entry=0x137c000) at /home/ralf.habacker/src/umbrello-3/umbrello/codeimport/import_utils.cpp:715
101      */
102     if (cols.size() > 2 && cols[0].startsWith(QLatin1String("#"))) {
103         QString file;
104         sequence = cols.takeFirst();
105         if (cols[cols.size()-2] == QLatin1String("at")) {
106             file = cols.takeLast();
107             cols.takeLast();
108         }
109         else if (cols[cols.size()-2] == QLatin1String("from")) {
110             module = cols.takeLast();
111             cols.takeLast();
112         }
113 
114         if (cols[1] == QLatin1String("in")) {
115             cols.takeFirst(); // remove address
116             cols.takeFirst(); // in
117         }
118 
119         identifier = cols.join(QLatin1String(" "));
120 
121         if (identifier.contains(QLatin1String("::"))) {
122             QStringList b = identifier.split( QLatin1String("::"));
123             // TODO handle multiple '::'
124             package = b.takeFirst();
125             method = b.join(QLatin1String("::"));
126         }
127         else {
128             method = identifier;
129             package = module;
130         }
131         return true;
132     }
133 
134     /**
135      * Qtcreator/gdb
136      * @verbatim
137      *  6	Driver::ParseHelper::ParseHelper	driver.cpp	299	0x634c44
138      * 31   g_main_context_dispatch /usr/lib64/libglib-2.0.so.0     0x7fffefe16316
139      * ignoring
140      * ... <more>                                                   0x7ffff41152d9
141      * 13  ??                                                       0x7ffff41152d9
142      * @endverbatim
143      */
144     else if (cols[cols.size()-1].startsWith(QLatin1String("0x"))) {
145         if (cols[0] == QLatin1String("...") || cols[1] == QLatin1String("??"))
146             return false;
147 
148         sequence = cols.takeFirst();
149         cols.takeLast();  // remove address
150 
151         QString line, file;
152         if (cols.size() == 2) {
153             module = cols.takeLast();
154             identifier = cols.join(QLatin1String(" "));
155         } else if (cols.size() > 2) {
156             line = cols.takeLast();
157             file = cols.takeLast();
158             identifier = cols.join(QLatin1String(" "));
159         }
160 
161         if (identifier.contains(QLatin1String("::"))) {
162             QStringList b = identifier.split( QLatin1String("::"));
163             method = b.takeLast();
164             package = b.join(QLatin1String("::"));
165         }
166         else {
167             method = identifier;
168             package = module;
169         }
170 
171         if (package.isEmpty() && !file.isEmpty())
172             package = file;
173 
174         if (!method.endsWith(QLatin1String(")")))
175             method.append(QLatin1String("()"));
176 
177         return true;
178     } else if (cols[cols.size()-1].contains(QLatin1String("::"))) {
179        QStringList b = s.split( QLatin1String("::"));
180        method = b.takeLast();
181        package = b.join(QLatin1String("::"));
182        return true;
183     }
184     error = QLatin1String("unsupported line format");
185     return false;
186 }
187 
188 /**
189  * Import sequence diagram entries from a string list.
190  *
191  * @param lines String list with sequences
192  * @param scene The diagram to import the sequences into.
193  * @param fileName The filename the sequences are imported from
194  * @return true Import was successful.
195  * @return false Import failed.
196  */
importSequences(const QStringList & lines,UMLScene * scene,const QString & fileName)197 bool importSequences(const QStringList &lines, UMLScene *scene, const QString &fileName)
198 {
199     // object widget cache map
200     QMap<QString, ObjectWidget*> objectsMap;
201 
202     // create "client widget"
203     UMLDoc *umldoc = UMLApp::app()->document();
204     QString name(QLatin1String("client"));
205     UMLObject *left = umldoc->findUMLObject(name, UMLObject::ot_Class);
206     if (!left ) {
207         left = new UMLObject(0, name);
208         left->setBaseType(UMLObject::ot_Class);
209     }
210 
211     ObjectWidget *leftWidget = (ObjectWidget *)Widget_Factory::createWidget(scene, left);
212     leftWidget->activate();
213     // required to be savable
214     scene->addWidgetCmd(leftWidget);
215     objectsMap[name] = leftWidget;
216 
217     ObjectWidget *rightWidget = 0;
218     ObjectWidget *mostRightWidget = leftWidget;
219     MessageWidget *messageWidget = 0;
220     // for further processing
221     MessageWidgetList messages;
222 
223     QStringList l;
224     SequenceLineFormat format = detectSequenceLineFormat(lines);
225     if (format == GDB || format == QtCreatorGDB)
226         foreach(const QString &s, lines)
227             l.push_front(s);
228     else
229         l = lines;
230 
231     // for each line
232     int index = 1;
233     foreach(const QString &line, l) {
234         QString stackframe, package, method, error;
235 
236         if (!parseSequenceLine(line, stackframe, package, method, error)) {
237             if (!error.isEmpty()) {
238                 QString item = QString::fromLatin1("%1:%2:%3: %4: %5")
239                         .arg(fileName).arg(index)
240                         .arg(1).arg(line).arg(error);
241                 UMLApp::app()->logWindow()->addItem(item);
242             }
243             continue;
244         }
245 
246         bool createObject = false;
247         if (package.contains(method))
248             createObject = true;
249 
250         if (package.isEmpty())
251             package = QLatin1String("unknown");
252 
253         // get or create right object widget
254         if (objectsMap.contains(package)) {
255             rightWidget = objectsMap[package];
256         } else {
257             UMLFolder *logicalView = UMLApp::app()->document()->rootFolder(Uml::ModelType::Logical);
258             UMLObject *right = Import_Utils::createUMLObjectHierarchy(UMLObject::ot_Class, package, logicalView);
259 
260             rightWidget = (ObjectWidget *)Widget_Factory::createWidget(scene, right);
261             rightWidget->setX(mostRightWidget->x() + mostRightWidget->width() + 10);
262             rightWidget->activate();
263             objectsMap[package] = rightWidget;
264             scene->addWidgetCmd(rightWidget);
265             mostRightWidget = rightWidget;
266         }
267 
268         // create message
269         int y = 10 + (messageWidget ? messageWidget->y() + messageWidget->height() : 20);
270         messageWidget = new MessageWidget(scene, leftWidget, rightWidget, y,
271                                           createObject ? Uml::SequenceMessage::Creation : Uml::SequenceMessage::Synchronous);
272         messageWidget->setCustomOpText(method);
273         messageWidget->setSequenceNumber(QString::number(index++));
274         messageWidget->calculateWidget();
275         messageWidget->activate();
276         messageWidget->setY(y);
277         // to make it savable
278         scene->addWidgetCmd(messageWidget);
279         messages.insert(0, messageWidget);
280 
281         leftWidget = rightWidget;
282     }
283 
284     if (messages.isEmpty())
285         return false;
286 
287     // adjust vertical position
288     foreach(MessageWidget *w, messages) {
289         w->setY(w->y() + 20);
290     }
291 
292     // adjust heights starting from the last message
293     MessageWidget *previous = messages.takeFirst();
294     foreach(MessageWidget *w, messages) {
295         w->setSize(w->width(), previous->y() - w->y() + previous->height() + 5);
296         // adjust vertical line length of object widgets
297         w->objectWidget(Uml::RoleType::A)->slotMessageMoved();
298         w->objectWidget(Uml::RoleType::B)->slotMessageMoved();
299         previous = w;
300     }
301     return true;
302 }
303 
304 /**
305  * Import sequence diagram entries from a string list.
306  *
307  * @param lines String list with sequences
308  * @param scene The diagram to import the sequences into.
309  * @return true Import was successful.
310  * @return false Import failed.
311  */
importGraph(const QStringList & lines,UMLScene * scene,const QString & fileName)312 bool importGraph(const QStringList &lines, UMLScene *scene, const QString &fileName)
313 {
314     if (scene->isSequenceDiagram())
315         return importSequences(lines, scene);
316     else if (scene->type() != Uml::DiagramType::Class)
317         return false;
318 
319     UMLDoc *umldoc = UMLApp::app()->document();
320 
321     UMLWidget *lastWidget = 0;
322     UMLClassifier *c = 0;
323     QString methodIdentifier(QLatin1String("()"));
324     QMap<QString, QPointer<UMLWidget>> widgetList;
325     int lineNumber = 0;
326     // for each line
327     foreach(const QString &line, lines) {
328         lineNumber++;
329         if (line.trimmed().isEmpty() || line.startsWith(QLatin1Char('#')) || line.startsWith(QLatin1String("//")))
330             continue;
331         QStringList l = line.split(QLatin1String(" "));
332         if (l.size() == 1) {
333             UMLObject *o = umldoc->findUMLObject(l[0], UMLObject::ot_Class);
334             if (!o)
335                 o = Object_Factory::createUMLObject(UMLObject::ot_Class, l[0]);
336             c = o->asUMLClassifier();
337             // TODO: avoid multiple inserts
338             UMLWidget *w = Widget_Factory::createWidget(scene, o);
339             if (lastWidget)
340                 w->setX(lastWidget->x() + lastWidget->width() + 10);
341             scene->setupNewWidget(w, false);
342             scene->createAutoAssociations(w);
343             scene->createAutoAttributeAssociations2(w);
344             widgetList[l[0]] = w;
345             lastWidget = w;
346         } else if (l.size() == 3 && l[1].startsWith(QLatin1Char('-'))) { // associations
347             UMLObject *o1 = umldoc->findUMLObject(l[0], UMLObject::ot_Class);
348             if (!o1)
349                 o1 = Object_Factory::createUMLObject(UMLObject::ot_Class, l[0]);
350             UMLObject *o2 = umldoc->findUMLObject(l[2], UMLObject::ot_Class);
351             if (!o2)
352                 o2 = Object_Factory::createUMLObject(UMLObject::ot_Class, l[2]);
353             bool swapObjects = false;
354             Uml::AssociationType::Enum type = Uml::AssociationType::Unknown;
355             UMLAssociation *assoc = 0;
356             bool newAssoc = false;
357             if (l[1] == QLatin1String("---")) {
358                 type = Uml::AssociationType::Association;
359             } else if (l[1] == QLatin1String("-->")) {
360                 type = Uml::AssociationType::UniAssociation;
361             } else if (l[1] == QLatin1String("-<>")) {
362                 type = Uml::AssociationType::Aggregation;
363                 swapObjects = true;
364             } else if (l[1] == QLatin1String("--*")) {
365                 type = Uml::AssociationType::Composition;
366                 swapObjects = true;
367             } else if (l[1] == QLatin1String("-|>")) {
368                 type = Uml::AssociationType::Generalization;
369             }
370             QPointer<UMLWidget> w1 = 0;
371             QPointer<UMLWidget> w2 = 0;
372             bool error = false;
373             if (swapObjects) {
374                 w1 = widgetList[l[2]];
375                 w2 = widgetList[l[0]];
376                 if (w1 && w2) {
377                     if (!assoc) {
378                         assoc = umldoc->findAssociation(type, o1, o2);
379                         if (!assoc) {
380                             assoc = new UMLAssociation(type, o1, o2);
381                             newAssoc = true;
382                         }
383                     }
384                 }
385                 else
386                     error = true;
387             } else {
388                 w1 = widgetList[l[0]];
389                 w2 = widgetList[l[2]];
390                 if (w1 && w2) {
391                     if (!assoc) {
392                         assoc = umldoc->findAssociation(type, o2, o1);
393                         if (!assoc) {
394                             assoc = new UMLAssociation(type, o2, o1);
395                             newAssoc = true;
396                         }
397                     }
398                 }
399                 else
400                     error = true;
401             }
402             if (!error) {
403                 if (newAssoc) {
404                     assoc->setUMLPackage(umldoc->rootFolder(Uml::ModelType::Logical));
405                     umldoc->addAssociation(assoc);
406                 }
407                 AssociationWidget* aw = AssociationWidget::create(scene, w1, type, w2, assoc);
408                 scene->addAssociation(aw);
409             } else {
410                 if (assoc)
411                     delete assoc;
412                 QString item = QString::fromLatin1("%1:%2:%3: %4: %5")
413                         .arg(fileName).arg(lineNumber)
414                         .arg(1).arg(line).arg(QLatin1String("error:could not add association"));
415                 UMLApp::app()->logWindow()->addItem(item);
416             }
417         } else if (l[0].isEmpty() && c && l.size() == 2) {
418             QString name = l.last();
419             if (name.contains(methodIdentifier)) {
420                 name.remove(methodIdentifier);
421                 UMLOperation *m = Import_Utils::makeOperation(c, name);
422                 Import_Utils::insertMethod(c, m, Uml::Visibility::Public, QLatin1String("void"), false, false);
423             } else {
424                 Import_Utils::insertAttribute(c, Uml::Visibility::Public, name, QLatin1String("int"));
425             }
426         } else if (l[0].isEmpty() && c && l.size() >= 3) {
427             QString name = l.takeLast();
428             l.takeFirst();
429             QString v = l.first().toLower();
430             Uml::Visibility::Enum visibility = Uml::Visibility::fromString(v, true);
431             if (visibility == Uml::Visibility::Unknown)
432                 visibility = Uml::Visibility::Public;
433             else
434                 l.takeFirst();
435             QString type = l.join(QLatin1String(" "));
436             if (name.contains(methodIdentifier)) {
437                 name.remove(methodIdentifier);
438                 UMLOperation *m = Import_Utils::makeOperation(c, name);
439                 Import_Utils::insertMethod(c, m, visibility, type, false, false);
440             } else {
441                 Import_Utils::insertAttribute(c, visibility, name, type);
442             }
443         } else {
444             QString item = QString::fromLatin1("%1:%2:%3: %4: %5")
445                     .arg(fileName).arg(lineNumber)
446                     .arg(1).arg(line).arg(QLatin1String("syntax error"));
447             UMLApp::app()->logWindow()->addItem(item);
448         }
449     }
450     return true;
451 }
452 
453 /**
454  * Import graph entries from clipboard
455  *
456  * @param mimeData instance of mime data to import from
457  * @param scene The diagram to import the graph into.
458  * @return true Import successful.
459  * @return false Import failed.
460  */
importGraph(const QMimeData * mimeData,UMLScene * scene)461 bool importGraph(const QMimeData* mimeData, UMLScene *scene)
462 {
463     QString requestedFormat = QLatin1String("text/plain");
464     if (!mimeData->hasFormat(requestedFormat))
465         return false;
466 
467     QByteArray payload = mimeData->data(requestedFormat);
468     if (!payload.size()) {
469         return false;
470     }
471     QString data = QString::fromUtf8(payload);
472     QStringList lines = data.split(QLatin1String("\n"));
473 
474     UMLDoc *doc = UMLApp::app()->document();
475     doc->beginPaste();
476     bool result = false;
477     if (scene->isSequenceDiagram())
478         result = importSequences(lines, scene);
479     else
480         result = importGraph(lines, scene);
481     doc->endPaste();
482     return result;
483 }
484 
485 /**
486  * Import graph entries from file
487  *
488  * @param fileName filename to import the graph from.
489  * @param scene The diagram to import the graph into.
490  * @return true Import successful.
491  * @return false Import failed.
492  */
importGraph(const QString & fileName,UMLScene * scene)493 bool importGraph(const QString &fileName, UMLScene *scene)
494 {
495     QFile file(fileName);
496 
497     if(!file.open(QIODevice::ReadOnly))
498         return false;
499 
500     QStringList lines;
501     QTextStream in(&file);
502     in.setCodec("UTF-8");
503     while (!in.atEnd()) {
504         lines.append(in.readLine());
505     }
506     return importGraph(lines, scene, fileName);
507 }
508 
509 /**
510  * Check if name for a diagram is unique
511  *
512  * @param type type of diagram to check (set to undefined if to check against all diagrams)
513  * @param name name of diagram to check
514  * @return true - name is unique
515  * @return false - name is not unique
516  */
isUniqueDiagramName(Uml::DiagramType::Enum type,QString & name)517 bool isUniqueDiagramName(Uml::DiagramType::Enum type, QString &name)
518 {
519     bool found = false;
520     foreach (UMLView *view, UMLApp::app()->document()->viewIterator()) {
521         if (type == Uml::DiagramType::Undefined || view->umlScene()->type() == type) {
522             if (view->umlScene()->name() == name)
523                 found = true;
524         }
525     }
526     return !found;
527 }
528 
529 }  // end namespace Diagram_Utils
530