1 /*
2 SPDX-License-Identifier: GPL-2.0-or-later
3 SPDX-FileCopyrightText: 2005-2021 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5
6 // own header
7 #include "idlimport.h"
8
9 // app includes
10 #include "attribute.h"
11 #include "classifier.h"
12 #include "codeimpthread.h"
13 #include "debug_utils.h"
14 #include "enum.h"
15 #include "import_utils.h"
16 #include "object_factory.h"
17 #include "operation.h"
18 #include "package.h"
19 #include "stereotype.h"
20 #include "uml.h"
21 #include "umldoc.h"
22 #include "umlpackagelist.h"
23
24 // kde includes
25 #if QT_VERSION < 0x050000
26 #include <KStandardDirs>
27 #endif
28
29 // qt includes
30 #include <QProcess>
31 #include <QRegExp>
32 #if QT_VERSION >= 0x050000
33 #include <QStandardPaths>
34 #endif
35 #include <QStringList>
36
37 #include <stdio.h>
38
39 QString IDLImport::m_preProcessor;
40 QStringList IDLImport::m_preProcessorArguments;
41 bool IDLImport::m_preProcessorChecked = false;
42
IDLImport(CodeImpThread * thread)43 IDLImport::IDLImport(CodeImpThread* thread) : NativeImportBase(QLatin1String("//"), thread)
44 {
45 m_doc = UMLApp::app()->document();
46 m_isOneway = m_isReadonly = m_isAttribute = m_isUnionDefault = false;
47 setMultiLineComment(QLatin1String("/*"), QLatin1String("*/"));
48
49 // we do not want to find the executable on each imported file
50 if (m_preProcessorChecked) {
51 m_enabled = !m_preProcessor.isEmpty();
52 return;
53 }
54
55 QStringList arguments;
56 #if QT_VERSION >= 0x050000
57 QString executable = QStandardPaths::findExecutable(QLatin1String("cpp"));
58 #else
59 QString executable = KStandardDirs::findExe(QLatin1String("cpp"));
60 #endif
61 if (!executable.isEmpty()) {
62 arguments << QLatin1String("-C"); // -C means "preserve comments"
63 }
64 #ifdef Q_OS_WIN
65 else {
66 #if QT_VERSION >= 0x050000
67 executable = QStandardPaths::findExecutable(QLatin1String("cl"));
68 #else
69 executable = KStandardDirs::findExe(QLatin1String("cl"));
70 #endif
71 if (executable.isEmpty()) {
72 QString path = QLatin1String(qgetenv("VS100COMNTOOLS").constData());
73 if (!path.isEmpty())
74 #if QT_VERSION >= 0x050000
75 executable = QStandardPaths::findExecutable(QLatin1String("cl"), QStringList() << path + QLatin1String("/../../VC/bin"));
76 #else
77 executable = KStandardDirs::findExe(QLatin1String("cl"), path + QLatin1String("/../../VC/bin"));
78 #endif
79 }
80 if (!executable.isEmpty()) {
81 arguments << QLatin1String("-E"); // -E means "preprocess to stdout"
82 }
83 }
84 #endif
85 if (!executable.isEmpty()) {
86 m_preProcessor = executable;
87 m_preProcessorArguments = arguments;
88 }
89 else {
90 log("Error: Cannot find any of the supported preprocessors (gcc, Microsoft Visual Studio 2010)");
91 m_enabled = false;
92 }
93 m_preProcessorChecked = true;
94 }
95
~IDLImport()96 IDLImport::~IDLImport()
97 {
98 }
99
100 /// Check for split type names (e.g. unsigned long long)
joinTypename()101 QString IDLImport::joinTypename()
102 {
103 QString typeName = m_source[m_srcIndex];
104 if (m_source[m_srcIndex] == QLatin1String("unsigned"))
105 typeName += QLatin1Char(' ') + advance();
106 if (m_source[m_srcIndex] == QLatin1String("long") &&
107 (m_source[m_srcIndex + 1] == QLatin1String("long") ||
108 m_source[m_srcIndex + 1] == QLatin1String("double")))
109 typeName += QLatin1Char(' ') + advance();
110 return typeName;
111 }
112
113 /**
114 * Override operation from NativeImportBase.
115 */
preprocess(QString & line)116 bool IDLImport::preprocess(QString& line)
117 {
118 // Ignore C preprocessor generated lines.
119 if (line.startsWith(QLatin1Char('#')))
120 return true; // done
121 return NativeImportBase::preprocess(line);
122 }
123
124 /**
125 * Implement abstract operation from NativeImportBase.
126 */
fillSource(const QString & word)127 void IDLImport::fillSource(const QString& word)
128 {
129 QString lexeme;
130 const uint len = word.length();
131 for (uint i = 0; i < len; ++i) {
132 QChar c = word[i];
133 if (c.isLetterOrNumber() || c == QLatin1Char('_')) {
134 lexeme += c;
135 } else if (c == QLatin1Char(':') && i < len-1 && word[i + 1] == QLatin1Char(':')) {
136 // compress scoped name into lexeme
137 lexeme += QLatin1String("::");
138 i++;
139 } else {
140 if (!lexeme.isEmpty()) {
141 m_source.append(lexeme);
142 lexeme.clear();
143 }
144 m_source.append(QString(c));
145 }
146 }
147 if (!lexeme.isEmpty())
148 m_source.append(lexeme);
149 }
150
151 /**
152 * Reimplement operation from NativeImportBase.
153 * Need to do this because we use the external C preprocessor.
154 */
parseFile(const QString & filename)155 bool IDLImport::parseFile(const QString& filename)
156 {
157 if (filename.contains(QLatin1Char('/'))) {
158 QString path = filename;
159 path.remove(QRegExp(QLatin1String("/[^/]+$")));
160 uDebug() << "adding path " << path;
161 Import_Utils::addIncludePath(path);
162 }
163 const QStringList includePaths = Import_Utils::includePathList();
164
165 if (m_preProcessor.isEmpty()) {
166 log("Error: no preprocessor installed, could not import file");
167 return false;
168 }
169 QStringList arguments(m_preProcessorArguments);
170
171 QProcess p(UMLApp::app());
172 for (QStringList::ConstIterator pathIt = includePaths.begin();
173 pathIt != includePaths.end(); ++pathIt) {
174 QString path = (*pathIt);
175 arguments << QLatin1String("-I") + path;
176 }
177 arguments << filename;
178 uDebug() << "importIDL: " << m_preProcessor << arguments;
179 p.start(m_preProcessor, arguments);
180 if (!p.waitForStarted()) {
181 log("Error: could not run preprocessor");
182 return false;
183 }
184
185 if (!p.waitForFinished()) {
186 log("Error: could not run preprocessor");
187 return false;
188 }
189 int exitCode = p.exitCode();
190 if (exitCode != 0) {
191 QString errMsg = QLatin1String("Error: preprocessor returned error");
192 log(errMsg + QString::number(exitCode));
193 return false;
194 }
195
196 QByteArray out = p.readAllStandardOutput();
197 QTextStream data(out);
198
199 // Scan the input file into the QStringList m_source.
200 m_source.clear();
201 while (!data.atEnd()) {
202 NativeImportBase::scan(data.readLine());
203 }
204
205 // Parse the QStringList m_source.
206 m_scope.clear();
207 pushScope(0); // global scope
208 const int srcLength = m_source.count();
209 for (m_srcIndex = 0; m_srcIndex < srcLength; ++m_srcIndex) {
210 const QString& keyword = m_source[m_srcIndex];
211 //uDebug() << QLatin1Char('"') << keyword << QLatin1Char('"');
212 if (keyword.startsWith(m_singleLineCommentIntro)) {
213 m_comment = keyword.mid(m_singleLineCommentIntro.length());
214 continue;
215 }
216 if (! parseStmt())
217 skipStmt();
218 m_currentAccess = Uml::Visibility::Public;
219 m_comment.clear();
220 }
221 return true;
222 }
223
224 /**
225 * Skip to the end of struct/union/valuetype/interface declaration.
226 *
227 * @return True for success, false for non recoverable syntax error
228 * related to brace matching (missing opening or closing brace).
229 */
skipStructure()230 bool IDLImport::skipStructure()
231 {
232 bool status = skipToClosing(QLatin1Char('{')); // skip to '}'
233 // Skipping of ';' after '}' is done by NativeImportBase::parseFile
234 return status;
235 }
236
237 /**
238 * Returns true if the given text is a valid IDL scoped name.
239 */
isValidScopedName(QString text)240 bool IDLImport::isValidScopedName(QString text) {
241 QRegExp validScopedName(QLatin1String("^[A-Za-z_:][A-Za-z0-9_:]*$"));
242 return text.contains(validScopedName);
243 }
244
245 /**
246 * Implement abstract operation from NativeImportBase.
247 * The function only returns false if an error is encountered from which
248 * no recovery is possible.
249 * On recoverable syntax errors, the function issues an error message,
250 * skips to the end of the offending declaration, and returns true.
251 * Returning true in spite of a local syntax error is done in the interest
252 * of best effort (returning false would abort the entire code import).
253 * A syntax error is typically unrecoverable when it involves imbalance of
254 * braces, in particular when the closing "}" of an opening "{" is missing.
255 */
parseStmt()256 bool IDLImport::parseStmt()
257 {
258 QString keyword = m_source[m_srcIndex];
259 const int srcLength = m_source.count();
260 uDebug() << "keyword is " << keyword;
261 if (keyword == QLatin1String("module")) {
262 const QString& name = advance();
263 UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Package,
264 name, currentScope(), m_comment);
265 pushScope(ns->asUMLPackage());
266 currentScope()->setStereotype(QLatin1String("idlModule"));
267 if (advance() != QLatin1String("{")) {
268 log(QLatin1String("Error: importIDL: unexpected: ") + m_source[m_srcIndex]);
269 skipStmt(QLatin1String("{"));
270 }
271 return true;
272 }
273 if (keyword == QLatin1String("interface")) {
274 const QString& name = advance();
275 UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Class,
276 name, currentScope(), m_comment);
277 m_klass = ns->asUMLClassifier();
278 m_klass->setStereotype(QLatin1String("idlInterface"));
279 m_klass->setAbstract(m_isAbstract);
280 m_isAbstract = false;
281 m_comment.clear();
282 if (advance() == QLatin1String(";")) // forward declaration
283 return true;
284 pushScope(m_klass);
285 if (m_source[m_srcIndex] == QLatin1String(":")) {
286 while (++m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String("{")) {
287 const QString& baseName = m_source[m_srcIndex];
288 Import_Utils::createGeneralization(m_klass, baseName);
289 if (advance() != QLatin1String(","))
290 break;
291 }
292 }
293 if (m_source[m_srcIndex] != QLatin1String("{")) {
294 log(QLatin1String("Error: importIDL: ignoring excess chars at ") + name);
295 skipStmt(QLatin1String("{"));
296 }
297 return true;
298 }
299 if (keyword == QLatin1String("struct") || keyword == QLatin1String("exception")) {
300 const QString& name = advance();
301 UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Class,
302 name, currentScope(), m_comment);
303 m_klass = ns->asUMLClassifier();
304 pushScope(m_klass);
305 if (keyword == QLatin1String("struct"))
306 m_klass->setStereotype(QLatin1String("idlStruct"));
307 else
308 m_klass->setStereotype(QLatin1String("idlException"));
309 if (advance() != QLatin1String("{")) {
310 log(QLatin1String("Error: importIDL: expecting '{' at ") + name);
311 skipStmt(QLatin1String("{"));
312 }
313 return true;
314 }
315 if (keyword == QLatin1String("union")) {
316 const QString& name = advance();
317 // create union type
318 UMLObject *u = Import_Utils::createUMLObject(UMLObject::ot_Class,
319 name, currentScope(), m_comment,
320 QLatin1String("idlUnion"));
321 if (!u) {
322 log(QLatin1String("Error: importIDL: Could not create union ") + name);
323 return skipStructure();
324 }
325 UMLClassifier *uc = u->asUMLClassifier();
326 if (!uc) {
327 log(QLatin1String("Error: importIDL(") + name +
328 QLatin1String("): Expecting type ot_Class, actual type is ") +
329 UMLObject::toString(u->baseType()));
330 return skipStructure();
331 }
332 if (advance() != QLatin1String("switch")) {
333 log(QLatin1String("Error: importIDL: expecting 'switch' after ") + name);
334 return skipStructure();
335 }
336 if (advance() != QLatin1String("(")) {
337 log(QLatin1String("Error: importIDL: expecting '(' after 'switch'"));
338 return skipStructure();
339 }
340 const QString& switchType = advance();
341 if (!isValidScopedName(switchType)) {
342 log(QLatin1String("Error: importIDL: expecting typename after 'switch'"));
343 return skipStructure();
344 }
345 // find or create discriminator type
346 UMLObject *dt = Import_Utils::createUMLObject(UMLObject::ot_Class,
347 switchType, currentScope(), m_comment);
348 if (!dt) {
349 log(QLatin1String("Error: importIDL(") + name +
350 QLatin1String("): Could not create switchtype ") + switchType);
351 return skipStructure();
352 }
353 UMLClassifier *discrType = dt->asUMLClassifier();
354 if (!discrType) {
355 log(QLatin1String("Error: importIDL(") + name +
356 QLatin1String(") swichtype: Expecting classifier type, found ") +
357 UMLObject::toString(dt->baseType()));
358 return skipStructure();
359 }
360 m_currentAccess = Uml::Visibility::Public;
361 UMLAttribute *discr = Import_Utils::insertAttribute(uc, m_currentAccess,
362 QLatin1String("discriminator"),
363 discrType, m_comment);
364 if (!discr) {
365 log(QLatin1String("Error: importIDL(") + name +
366 QLatin1String("): Could not create switch attribute"));
367 return skipStructure();
368 }
369 discr->setStereotype(QLatin1String("idlSwitch"));
370 if (advance() != QLatin1String(")")) {
371 log(QLatin1String("Error: importIDL: expecting ')' after switch type"));
372 return skipStructure();
373 }
374 if (advance() != QLatin1String("{")) {
375 log(QLatin1String("Error: importIDL(") + name +
376 QLatin1String("): expecting '{' after switch type"));
377 return skipStructure();
378 }
379 m_klass = uc;
380 pushScope(m_klass);
381 m_unionCases.clear();
382 m_isUnionDefault = false;
383 return true;
384 }
385 if (keyword == QLatin1String("case")) {
386 QString caseValue = advance();
387 if (!isValidScopedName(caseValue)) {
388 log(QLatin1String("Error: importIDL: expecting symbolic identifier after 'case'"));
389 skipStmt(QLatin1String(":"));
390 }
391 m_unionCases.append(caseValue);
392 if (advance() != QLatin1String(":")) {
393 log(QLatin1String("Error: importIDL: expecting ':' after 'case'"));
394 }
395 return true;
396 }
397 if (keyword == QLatin1String("default")) {
398 m_isUnionDefault = true;
399 if (advance() != QLatin1String(":")) {
400 log(QLatin1String("Error: importIDL: expecting ':' after 'default'"));
401 }
402 return true;
403 }
404 if (keyword == QLatin1String("enum")) {
405 const QString& name = advance();
406 UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Enum,
407 name, currentScope(), m_comment);
408 UMLEnum *enumType = ns->asUMLEnum();
409 if (enumType == 0)
410 enumType = Import_Utils::remapUMLEnum(ns, currentScope());
411 m_srcIndex++; // skip name
412 while (++m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String("}")) {
413 if (enumType != 0)
414 Import_Utils::addEnumLiteral(enumType, m_source[m_srcIndex]);
415 if (advance() != QLatin1String(","))
416 break;
417 }
418 skipStmt();
419 return true;
420 }
421 if (keyword == QLatin1String("typedef")) {
422 if (m_source[m_srcIndex + 2] == QLatin1String("<")) {
423 keyword = advance();
424 m_srcIndex += 2;
425 QString oldType;
426 QString bound;
427 if (keyword == QLatin1String("sequence")) {
428 oldType = joinTypename();
429 if (advance() == QLatin1String(",")) {
430 bound = advance();
431 m_srcIndex++; // position on ">"
432 }
433 } else if (keyword == QLatin1String("string")) {
434 bound = m_source[m_srcIndex];
435 m_srcIndex++; // position on ">"
436 } else {
437 log(QLatin1String("Error: parseStmt: Expecting 'sequence' or 'string', found ") +
438 keyword);
439 skipStmt();
440 return true;
441 }
442 const QString& newType = advance();
443 skipStmt();
444 UMLObject::ObjectType ot = (oldType.length() ? UMLObject::ot_Class : UMLObject::ot_Datatype);
445 UMLObject *pOld = m_doc->findUMLObject(oldType, UMLObject::ot_UMLObject, currentScope());
446 if (pOld == 0) {
447 pOld = Import_Utils::createUMLObject(ot, oldType, currentScope());
448 }
449 UMLObject *dt = Import_Utils::createUMLObject(ot, newType, currentScope(), m_comment);
450 if (!dt) {
451 log(QLatin1String("Error: importIDL(typedef ") + keyword +
452 QLatin1String("): Could not create datatype ") + newType);
453 return true;
454 }
455 QString stereoName = (oldType.length() ? QLatin1String("idlSequence") : QLatin1String("idlString"));
456 UMLStereotype *pStereo = m_doc->findStereotype(stereoName);
457 if (pStereo == 0) {
458 pStereo = m_doc->createStereotype(stereoName);
459 UMLStereotype::AttributeDef tagDef(QLatin1String("bound"), Uml::PrimitiveTypes::UnlimitedNatural);
460 // Empty bound stands for "unbounded".
461 pStereo->getAttributeDefs().append(tagDef);
462 }
463 if (oldType.length()) {
464 UMLAttribute *typeAttr = Object_Factory::createAttribute(dt, QLatin1String("members"), pOld);
465 Q_UNUSED(typeAttr);
466 /*
467 UMLClassifier *cl = dt->asUMLClassifier();
468 if (cl == 0) {
469 uError() << "parseStmt(typedef " << newType
470 << ") internal error: object returned by Import_Utils::createUMLObject is not a Class";
471 return false;
472 } */
473 }
474 dt->setUMLStereotype(pStereo);
475 dt->tags().append(bound);
476 return true;
477 }
478 m_srcIndex++;
479 const QString& oldType = joinTypename();
480 const QString& newType = advance();
481 uDebug() << "parseStmt(typedef) : oldType is " << oldType
482 << ", newType is " << newType
483 << ", scopeIndex is " << scopeIndex();
484 UMLObject::ObjectType ot = UMLObject::ot_Class;
485 UMLObject *pOld = m_doc->findUMLObject(oldType, UMLObject::ot_UMLObject, currentScope());
486 if (pOld) {
487 ot = pOld->baseType();
488 } else {
489 pOld = Import_Utils::createUMLObject(ot, oldType, currentScope());
490 }
491 UMLClassifier *oldClassifier = pOld->asUMLClassifier();
492 UMLObject *pNew = Import_Utils::createUMLObject(ot, newType, currentScope(), m_comment,
493 QLatin1String("idlTypedef")); /* stereotype */
494 UMLClassifier *newClassifier = pNew->asUMLClassifier();
495 if (oldClassifier == 0) {
496 log(QLatin1String("Error: importIDL(typedef ") + newType +
497 QLatin1String("): Origin type ") + oldType + QLatin1String(" is not a classifier"));
498 } else if (newClassifier == 0) {
499 log(QLatin1String("Error: importIDL(typedef ") + newType +
500 QLatin1String(") internal error: Import_Utils::createUMLObject did not return a classifier"));
501 } else {
502 Import_Utils::createGeneralization(newClassifier, oldClassifier);
503 }
504 skipStmt();
505 return true;
506 }
507 if (keyword == QLatin1String("const")) {
508 skipStmt();
509 return true;
510 }
511 if (keyword == QLatin1String("custom")) {
512 return true;
513 }
514 if (keyword == QLatin1String("abstract")) {
515 m_isAbstract = true;
516 return true;
517 }
518 if (keyword == QLatin1String("valuetype")) {
519 const QString& name = advance();
520 UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Class,
521 name, currentScope(), m_comment);
522 m_klass = ns->asUMLClassifier();
523 m_klass->setStereotype(QLatin1String("idlValue"));
524 m_klass->setAbstract(m_isAbstract);
525 m_isAbstract = false;
526 if (advance() == QLatin1String(";")) // forward declaration
527 return true;
528 pushScope(m_klass);
529 if (m_source[m_srcIndex] == QLatin1String(":")) {
530 if (advance() == QLatin1String("truncatable"))
531 m_srcIndex++;
532 while (m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String("{")) {
533 const QString& baseName = m_source[m_srcIndex];
534 Import_Utils::createGeneralization(m_klass, baseName);
535 if (advance() != QLatin1String(","))
536 break;
537 m_srcIndex++;
538 }
539 }
540 if (m_source[m_srcIndex] != QLatin1String("{")) {
541 log(QLatin1String("Error: importIDL: ignoring excess chars at ") + name);
542 skipStmt(QLatin1String("{"));
543 }
544 return true;
545 }
546 if (keyword == QLatin1String("public")) {
547 return true;
548 }
549 if (keyword == QLatin1String("private")) {
550 m_currentAccess = Uml::Visibility::Private;
551 return true;
552 }
553 if (keyword == QLatin1String("readonly")) {
554 m_isReadonly = true;
555 return true;
556 }
557 if (keyword == QLatin1String("attribute")) {
558 m_isAttribute = true;
559 return true;
560 }
561 if (keyword == QLatin1String("oneway")) {
562 m_isOneway = true;
563 return true;
564 }
565 if (keyword == QLatin1String("}")) {
566 if (scopeIndex())
567 m_klass = popScope()->asUMLClassifier();
568 else
569 log(QLatin1String("Error: importIDL: too many }"));
570 m_srcIndex++; // skip ';'
571 return true;
572 }
573 if (keyword == QLatin1String(";"))
574 return true;
575 // At this point, we expect `keyword' to be a type name
576 // (of a member of struct or valuetype, or return type
577 // of an operation.) Up next is the name of the attribute
578 // or operation.
579 if (! keyword.contains(QRegExp(QLatin1String("^\\w")))) {
580 log(QLatin1String("Error: importIDL: ignoring ") + keyword);
581 return false;
582 }
583 QString typeName = joinTypename();
584 QString name = advance();
585 if (name.contains(QRegExp(QLatin1String("\\W")))) {
586 log(QLatin1String("Error: importIDL: expecting name in ") + name);
587 return false;
588 }
589 // At this point we most definitely need a class.
590 if (m_klass == 0) {
591 log(QLatin1String("Error: importIDL: no class set for ") + name);
592 return false;
593 }
594 QString nextToken = advance();
595 if (nextToken == QLatin1String("(")) {
596 // operation
597 UMLOperation *op = Import_Utils::makeOperation(m_klass, name);
598 m_srcIndex++;
599 while (m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String(")")) {
600 const QString &direction = m_source[m_srcIndex++];
601 QString typeName = joinTypename();
602 const QString &parName = advance();
603 UMLAttribute *att = Import_Utils::addMethodParameter(op, typeName, parName);
604 Uml::ParameterDirection::Enum dir;
605 if (Model_Utils::stringToDirection(direction, dir))
606 att->setParmKind(dir);
607 else
608 log(QLatin1String("Error: importIDL: expecting parameter direction at ") +
609 direction);
610 if (advance() != QLatin1String(","))
611 break;
612 m_srcIndex++;
613 }
614 Import_Utils::insertMethod(m_klass, op, Uml::Visibility::Public, typeName,
615 false, false, false, false, false, m_comment);
616 if (m_isOneway) {
617 op->setStereotype(QLatin1String("oneway"));
618 m_isOneway = false;
619 }
620 skipStmt(); // skip possible "raises" clause
621 return true;
622 }
623 // At this point we know it's some kind of attribute declaration.
624 while (1) {
625 while (nextToken != QLatin1String(",") && nextToken != QLatin1String(";")) {
626 name += nextToken; // add possible array dimensions to `name'
627 nextToken = advance();
628 }
629 UMLAttribute *attr = Import_Utils::insertAttribute(m_klass, m_currentAccess, name, typeName, m_comment);
630 if (m_isReadonly) {
631 attr->setStereotype(QLatin1String("readonly"));
632 m_isReadonly = false;
633 }
634 if (m_unionCases.count()) {
635 const QString stereoName = QLatin1String("idlCase");
636 UMLStereotype *pStereo = m_doc->findStereotype(stereoName);
637 if (pStereo == 0) {
638 pStereo = m_doc->createStereotype(stereoName);
639 UMLStereotype::AttributeDef tagDef(QLatin1String("label"), Uml::PrimitiveTypes::String);
640 pStereo->getAttributeDefs().append(tagDef);
641 }
642 attr->setUMLStereotype(pStereo);
643 const QString caseLabels = m_unionCases.join(QChar(' '));
644 attr->tags().append(caseLabels);
645 m_unionCases.clear();
646 } else if (m_isUnionDefault) {
647 attr->setStereotype(QLatin1String("idlDefault"));
648 m_isUnionDefault = false;
649 }
650 if (nextToken != QLatin1String(","))
651 break;
652 name = advance();
653 nextToken = advance();
654 }
655 return true;
656 }
657
658