1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2003-2021 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5 
6 #include "idlwriter.h"
7 
8 #include "umldoc.h"
9 #include "classifier.h"
10 #include "debug_utils.h"
11 #include "enum.h"
12 #include "classifierlistitem.h"
13 #include "umlclassifierlistitemlist.h"
14 #include "package.h"
15 #include "association.h"
16 #include "attribute.h"
17 #include "operation.h"
18 
19 #include <KMessageBox>
20 
21 #include <QFile>
22 #include <QRegExp>
23 #include <QTextStream>
24 
IDLWriter()25 IDLWriter::IDLWriter() : SimpleCodeGenerator(false)
26 {
27 }
28 
~IDLWriter()29 IDLWriter::~IDLWriter()
30 {
31 }
32 
isOOClass(UMLClassifier * c)33 bool IDLWriter::isOOClass(UMLClassifier *c)
34 {
35     QString stype = c->stereotype();
36     QRegExp nonOO(QLatin1String("(Constant|Enum|Struct|Union|Sequence|Array|Typedef)$"),
37                   Qt::CaseInsensitive);
38     if (stype.contains(nonOO))
39         return false;
40 
41     // idlValue/CORBAValue, idlInterface/CORBAInterface, and empty/unknown stereotypes are
42     // assumed to designate OO classes.
43     return true;
44 }
45 
assocTypeIsMappableToAttribute(Uml::AssociationType::Enum at)46 bool IDLWriter::assocTypeIsMappableToAttribute(Uml::AssociationType::Enum at)
47 {
48     return (at == Uml::AssociationType::Aggregation || at == Uml::AssociationType::Association ||
49             at == Uml::AssociationType::Composition || at == Uml::AssociationType::UniAssociation);
50 }
51 
52 /**
53  * Returns "IDL".
54  */
language() const55 Uml::ProgrammingLanguage::Enum IDLWriter::language() const
56 {
57     return Uml::ProgrammingLanguage::IDL;
58 }
59 
computeAssocTypeAndRole(UMLAssociation * a,UMLClassifier * c,QString & typeName,QString & roleName)60 void IDLWriter::computeAssocTypeAndRole(UMLAssociation *a, UMLClassifier *c,
61                                         QString& typeName, QString& roleName)
62 {
63     // Determine which is the "remote" end of the association:
64     bool IAmRoleA = true;
65     UMLObject *other = a->getObject(Uml::RoleType::B);
66     Uml::AssociationType::Enum at = a->getAssocType();
67     if (c->name() == other->name()) {
68         if (at == Uml::AssociationType::Aggregation || at == Uml::AssociationType::Composition ||
69             at == Uml::AssociationType::UniAssociation) {
70             // Assuming unidirectional association, and we are
71             // at the "wrong" side.
72             // Returning roleName = QString() tells caller to
73             // skip this association at this side.
74             roleName.clear();
75             return;
76         }
77         IAmRoleA = false;
78         other = a->getObject(Uml::RoleType::A);
79     }
80     // Construct the type name:
81     typeName = cleanName(other->name());
82     QString multiplicity;
83     if (IAmRoleA)
84         multiplicity = a->getMultiplicity(Uml::RoleType::B);
85     else
86         multiplicity = a->getMultiplicity(Uml::RoleType::A);
87     if (!multiplicity.isEmpty() && multiplicity != QLatin1String("1"))
88         typeName.append(QLatin1String("Vector"));
89     // Construct the member name:
90     if (IAmRoleA)
91         roleName = a->getRoleName(Uml::RoleType::B);
92     else
93         roleName = a->getRoleName(Uml::RoleType::A);
94     if (roleName.isEmpty()) {
95         roleName = a->name();
96         if (roleName.isEmpty()) {
97             roleName = QLatin1String("m_") + typeName;
98         }
99     }
100 }
101 
102 /**
103  * Call this method to generate IDL code for a UMLClassifier.
104  * @param c the class to generate code for
105  */
writeClass(UMLClassifier * c)106 void IDLWriter::writeClass(UMLClassifier *c)
107 {
108     if (!c) {
109         uDebug() << "Cannot write class of NULL concept!";
110         return;
111     }
112 
113     const bool isClass = !c->isInterface();
114     QString classname = cleanName(c->name());
115 
116     //find an appropriate name for our file
117     QString fileName = findFileName(c, QLatin1String(".idl"));
118     if (fileName.isEmpty()) {
119         emit codeGenerated(c, false);
120         return;
121     }
122 
123     QFile file;
124     if (!openFile(file, fileName)) {
125         emit codeGenerated(c, false);
126         return;
127     }
128 
129     // Start generating the code.
130 
131     QTextStream idl(&file);
132     //try to find a heading file(license, comments, etc)
133     QString str;
134     str = getHeadingFile(QLatin1String(".idl"));
135     if (!str.isEmpty()) {
136         str.replace(QRegExp(QLatin1String("%filename%")), fileName);
137         str.replace(QRegExp(QLatin1String("%filepath%")), file.fileName());
138         idl << str << m_endl;
139     }
140 
141     // Write includes.
142     UMLPackageList includes;
143     findObjectsRelated(c, includes);
144     if (includes.count()) {
145         foreach (UMLPackage* conc, includes) {
146             if (conc->isUMLDatatype())
147                 continue;
148             QString incName = findFileName(conc, QLatin1String(".idl"));
149             if (!incName.isEmpty())
150                 idl << "#include \"" << incName << "\"" << m_endl;
151         }
152         idl << m_endl;
153     }
154 
155     // Generate the module declaration(s) for the package(s) in which
156     // we are embedded.
157     UMLPackageList pkgList = c->packages();
158 
159     foreach (UMLPackage* pkg,  pkgList) {
160         idl << indent() << "module " << pkg->name() << " {" << m_endl << m_endl;
161         m_indentLevel++;
162     }
163 
164     // Write class Documentation if non-empty or if force option set.
165     if (forceDoc() || !c->doc().isEmpty()) {
166         idl << "//" << m_endl;
167         idl << "// class " << classname << m_endl;
168         idl << formatDoc(c->doc(), QLatin1String("// "));
169         idl << m_endl;
170     }
171 
172     if (c->baseType() == UMLObject::ot_Enum) {
173         UMLClassifierListItemList litList = c->getFilteredList(UMLObject::ot_EnumLiteral);
174         uint i = 0;
175         idl << indent() << "enum " << classname << " {" << m_endl;
176         m_indentLevel++;
177         foreach (UMLClassifierListItem *lit, litList) {
178             QString enumLiteral = cleanName(lit->name());
179             idl << indent() << enumLiteral;
180             if (++i < (uint)litList.count())
181                 idl << ",";
182             idl << m_endl;
183         }
184         m_indentLevel--;
185         idl << indent() << "};" << m_endl << m_endl;
186         // Close the modules inside which we might be nested.
187         for (int i = 0; i < pkgList.count(); ++i) {
188             m_indentLevel--;
189             idl << indent() << "};" << m_endl << m_endl;
190         }
191         return;
192     }
193     if (! isOOClass(c)) {
194         QString stype = c->stereotype();
195         if (stype.contains(QLatin1String("Constant"))) {
196             uError() << "The stereotype " << stype << " cannot be applied to "
197                      << c->name() << ", but only to attributes.";
198             return;
199         }
200         if (!isClass) {
201             uError() << "The stereotype " << stype
202                      << " cannot be applied to " << c->name()
203                      << ", but only to classes.";
204             return;
205         }
206         if (stype.contains(QLatin1String("Enum"))) {
207             UMLAttributeList atl = c->getAttributeList();
208 
209             idl << indent() << "enum " << classname << " {" << m_endl;
210             m_indentLevel++;
211             uint i = 0;
212             foreach (UMLAttribute* at, atl) {
213                 QString enumLiteral = cleanName(at->name());
214                 idl << indent() << enumLiteral;
215                 if (++i < (uint)atl.count())
216                     idl << ",";
217                 idl << m_endl;
218             }
219             m_indentLevel--;
220             idl << indent() << "};" << m_endl << m_endl;
221         } else if (stype.contains(QLatin1String("Struct"))) {
222             UMLAttributeList atl = c->getAttributeList();
223 
224             idl << indent() << "struct " << classname << " {" << m_endl;
225             m_indentLevel++;
226             foreach (UMLAttribute* at, atl) {
227                 QString name = cleanName(at->name());
228                 idl << indent() << at->getTypeName() << " " << name << ";" << m_endl;
229                 // Initial value not possible in IDL.
230             }
231             UMLAssociationList compositions = c->getCompositions();
232             if (!compositions.isEmpty()) {
233                 idl << indent() << "// Compositions." << m_endl;
234                 foreach (UMLAssociation *a, compositions) {
235                     QString memberType, memberName;
236                     computeAssocTypeAndRole(a, c, memberType, memberName);
237                     idl << indent() << memberType << " " << memberName << ";" << m_endl;
238                 }
239             }
240             UMLAssociationList aggregations = c->getAggregations();
241             if (!aggregations.isEmpty()) {
242                 idl << indent() << "// Aggregations." << m_endl;
243                 foreach (UMLAssociation *a, aggregations) {
244                     QString memberType, memberName;
245                     computeAssocTypeAndRole(a, c, memberType, memberName);
246                     idl << indent() << memberType << " " << memberName << ";" << m_endl;
247                 }
248             }
249             m_indentLevel--;
250             idl << indent() << "};" << m_endl << m_endl;
251         } else if (stype.contains(QLatin1String("Union"))) {
252             // idl << indent() << "// " << stype << " " << c->name() << " is Not Yet Implemented" << m_endl << m_endl;
253             UMLAttributeList atl = c->getAttributeList();
254             if (atl.count()) {
255                 UMLAttribute *discrim = atl.front();
256                 idl << indent() << "union " << c->name() << " switch (" << discrim->getTypeName()
257                                 << ") {"  << m_endl << m_endl;
258                 atl.pop_front();
259                 m_indentLevel++;
260                 foreach (UMLAttribute *at, atl) {
261                     QString attName = cleanName(at->name());
262                     const QStringList& tags = at->tags();
263                     if (tags.count()) {
264                         const QString& caseVals = tags.front();
265                         foreach (QString caseVal, caseVals.split(QLatin1Char(' '))) {
266                             idl << indent() << "case " << caseVal << ":" << m_endl;
267                         }
268                     } else {
269                         // uError() << "missing 'case' tag at union attribute " << attName;
270                         idl << indent() << "default:" << m_endl;
271                     }
272                     idl << indent() << m_indentation << at->getTypeName() << " " << attName << ";"
273                         << m_endl << m_endl;
274                 }
275                 m_indentLevel--;
276                 idl << indent() << "};" << m_endl << m_endl;
277             } else {
278                 uError() << "Empty <<union>> " << c->name();
279                 idl << indent() << "// <<union>> " << c->name() << " is empty, please check model" << m_endl;
280                 idl << indent() << "union " << c->name() << ";" << m_endl << m_endl;
281             }
282         } else if (stype.contains(QLatin1String("Typedef"))) {
283             UMLClassifierList superclasses = c->getSuperClasses();
284             if (superclasses.count()) {
285                 UMLClassifier* firstParent = superclasses.first();
286                 idl << indent() << "typedef " << firstParent->name() << " "
287                     << c->name() << ";" << m_endl << m_endl;
288             } else {
289                 uError() << "typedef " << c->name() << " parent (origin type) is missing";
290                 idl << indent() << "// typedef origin type is missing, please check model" << m_endl;
291                 idl << indent() << "typedef long " << c->name() << ";" << m_endl << m_endl;
292             }
293         } else {
294             idl << indent() << "// " << stype << ": Unknown stereotype" << m_endl << m_endl;
295         }
296         // Close the modules inside which we might be nested.
297         for (int i = 0; i < pkgList.count(); ++i) {
298             m_indentLevel--;
299             idl << indent() << "};" << m_endl << m_endl;
300         }
301         return;
302     }
303 
304     idl << indent();
305     if (c->isAbstract())
306         idl << "abstract ";
307     bool isValuetype = (c->stereotype().contains(QLatin1String("Value")));
308     if (isValuetype)
309         idl << "valuetype ";
310     else
311         idl << "interface ";
312     idl << c->name();
313     UMLClassifierList superclasses = c->getSuperClasses();
314     if (! superclasses.isEmpty()) {
315         idl << " : ";
316         int count = superclasses.count();
317         foreach(UMLClassifier* parent, superclasses) {
318             count--;
319             idl << parent->fullyQualifiedName(QLatin1String("::"));
320             if (count)
321                 idl << ", ";
322         }
323     }
324     idl << " {" << m_endl << m_endl;
325     m_indentLevel++;
326 
327     // Generate auxiliary declarations for multiplicity of associations
328 
329     bool didComment = false;
330     UMLAssociationList assocs = c->getAssociations();
331     foreach (UMLAssociation *a, assocs) {
332         if (! assocTypeIsMappableToAttribute(a->getAssocType()))
333             continue;
334         QString multiplicity = a->getMultiplicity(Uml::RoleType::A);
335         if (multiplicity.isEmpty() || multiplicity == QLatin1String("1"))
336             continue;
337         if (!didComment) {
338             idl << indent() << "// Types for association multiplicities" << m_endl << m_endl;
339             didComment = true;
340         }
341         UMLClassifier* other = (UMLClassifier*)a->getObject(Uml::RoleType::A);
342         QString bareName = cleanName(other->name());
343         idl << indent() << "typedef sequence<" << other->fullyQualifiedName(QLatin1String("::"))
344             << "> " << bareName << "Vector;" << m_endl << m_endl;
345     }
346 
347     // Generate public attributes.
348     if (isClass) {
349         UMLAttributeList atl = c->getAttributeList();
350         if (forceSections() || atl.count()) {
351             idl << indent() << "// Attributes:" << m_endl << m_endl;
352             foreach (UMLAttribute *at, atl) {
353                 QString attName = cleanName(at->name());
354                 Uml::Visibility::Enum scope = at->visibility();
355                 idl << indent();
356                 if (isValuetype) {
357                     if (scope == Uml::Visibility::Public)
358                         idl << "public ";
359                     else
360                         idl << "private ";
361                 } else {
362                     if (scope != Uml::Visibility::Public) {
363                         idl << "// visibility should be: "
364                             << Uml::Visibility::toString(scope)
365                             << m_endl;
366                         idl << indent();
367                     }
368                     idl << "attribute ";
369                 }
370                 idl << at->getTypeName() << " " << attName << ";"
371                     << m_endl << m_endl;
372             }
373         }
374     }
375 
376     // Generate public operations.
377     UMLOperationList opl(c->getOpList());
378     UMLOperationList oppub;
379 
380     foreach (UMLOperation* op, opl) {
381           if (op->visibility() == Uml::Visibility::Public)
382             oppub.append(op);
383     }
384     if (forceSections() || oppub.count()) {
385         idl << indent() << "// Public methods:" << m_endl << m_endl;
386         foreach (UMLOperation* op, oppub)
387             writeOperation(op, idl);
388         idl << m_endl;
389     }
390 
391     if (forceSections() || !assocs.isEmpty()) {
392         idl << indent() << "// Associations:" << m_endl << m_endl;
393         foreach (UMLAssociation* a, assocs) {
394             Uml::AssociationType::Enum at = a->getAssocType();
395             if (! assocTypeIsMappableToAttribute(at))
396                 continue;
397             QString typeName, roleName;
398             computeAssocTypeAndRole(a, c, typeName, roleName);
399             if (roleName.isEmpty())  // presumably because we are at the "wrong" end
400                 continue;
401             idl << indent() << "// " << Uml::AssociationType::toString(at) << m_endl;
402             idl << indent();
403             if (isValuetype)
404                 idl << "public ";
405             else
406                 idl << "attribute ";
407             idl << typeName << " " << roleName << ";" << m_endl;
408         }
409         idl << m_endl;
410     }
411 
412     m_indentLevel--;
413     idl << indent() << "};" << m_endl << m_endl;
414 
415     // Close the modules inside which we might be nested.
416     for (int i = 0; i < pkgList.count(); ++i) {
417         m_indentLevel--;
418         idl << indent() << "};" << m_endl << m_endl;
419     }
420     file.close();
421     emit codeGenerated(c, true);
422     emit showGeneratedFile(file.fileName());
423 }
424 
425 /**
426  * Write one operation.
427  * @param op the class for which we are generating code
428  * @param idl the stream associated with the output file
429  */
writeOperation(UMLOperation * op,QTextStream & idl,bool is_comment)430 void IDLWriter::writeOperation(UMLOperation *op, QTextStream &idl, bool is_comment)
431 {
432     UMLAttributeList atl = op->getParmList();
433     QString rettype = op->getTypeName();
434 
435     if (rettype.isEmpty())
436         rettype = QLatin1String("void");
437     idl << indent();
438     if (is_comment)
439         idl << "// ";
440     idl << rettype << " " << cleanName(op->name()) << " (";
441     if (atl.count()) {
442         idl << m_endl;
443         m_indentLevel++;
444         uint i = 0;
445         foreach (UMLAttribute *at, atl) {
446             idl << indent();
447             if (is_comment)
448                 idl << "// ";
449             Uml::ParameterDirection::Enum pk = at->getParmKind();
450             if (pk == Uml::ParameterDirection::Out)
451                 idl << "out ";
452             else if (pk == Uml::ParameterDirection::InOut)
453                 idl << "inout ";
454             else
455                 idl << "in ";
456             idl << at->getTypeName() << " " << cleanName(at->name());
457             if (++i < (uint)atl.count())
458                 idl << "," << m_endl;
459         }
460         m_indentLevel--;
461     }
462     idl << ");" << m_endl << m_endl;
463 }
464 
defaultDatatypes() const465 QStringList IDLWriter::defaultDatatypes() const
466 {
467     QStringList l;
468     l.append(QLatin1String("boolean"));
469     l.append(QLatin1String("char"));
470     l.append(QLatin1String("octet"));
471     l.append(QLatin1String("short"));
472     l.append(QLatin1String("unsigned short"));
473     l.append(QLatin1String("long"));
474     l.append(QLatin1String("unsigned long"));
475     l.append(QLatin1String("long long"));
476     l.append(QLatin1String("unsigned long long"));
477     l.append(QLatin1String("float"));
478     l.append(QLatin1String("double"));
479     l.append(QLatin1String("long double"));
480     l.append(QLatin1String("string"));
481     l.append(QLatin1String("any"));
482     return l;
483 }
484 
485 /**
486  * Get list of reserved keywords.
487  */
reservedKeywords() const488 QStringList IDLWriter::reservedKeywords() const
489 {
490     static QStringList keywords;
491 
492     if (keywords.isEmpty()) {
493         keywords
494           << QLatin1String("any")
495           << QLatin1String("attribute")
496           << QLatin1String("boolean")
497           << QLatin1String("case")
498           << QLatin1String("char")
499           << QLatin1String("const")
500           << QLatin1String("context")
501           << QLatin1String("default")
502           << QLatin1String("double")
503           << QLatin1String("enum")
504           << QLatin1String("exception")
505           << QLatin1String("FALSE")
506           << QLatin1String("float")
507           << QLatin1String("in")
508           << QLatin1String("inout")
509           << QLatin1String("interface")
510           << QLatin1String("long")
511           << QLatin1String("module")
512           << QLatin1String("octet")
513           << QLatin1String("oneway")
514           << QLatin1String("out")
515           << QLatin1String("raises")
516           << QLatin1String("readonly")
517           << QLatin1String("sequence")
518           << QLatin1String("short")
519           << QLatin1String("string")
520           << QLatin1String("struct")
521           << QLatin1String("switch")
522           << QLatin1String("TRUE")
523           << QLatin1String("typedef")
524           << QLatin1String("union")
525           << QLatin1String("unsigned")
526           << QLatin1String("void");
527     }
528 
529     return keywords;
530 }
531