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