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