/*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2007 * * Umbrello UML Modeller Authors * ***************************************************************************/ // // C++ Implementation: csharpwriter // #include "csharpwriter.h" #include #include #include #include "../uml.h" #include "../umldoc.h" #include "../folder.h" #include "../classifier.h" #include "../association.h" #include "../attribute.h" #include "../operation.h" #include "../umlnamespace.h" static const char *reserved_words[] = { "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while", 0 }; CSharpWriter::CSharpWriter() : SimpleCodeGenerator() { } CSharpWriter::~CSharpWriter() { } TQStringList CSharpWriter::defaultDatatypes() { TQStringList l; l.append("bool"); l.append("byte"); l.append("char"); l.append("decimal"); l.append("double"); l.append("fixed"); l.append("float"); l.append("fixed"); l.append("float"); l.append("int"); l.append("long"); l.append("object"); l.append("sbyte"); l.append("short"); l.append("string"); l.append("uint"); l.append("ulong"); l.append("ushort"); return l; } void CSharpWriter::writeClass(UMLClassifier *c) { if (!c) { kDebug()<<"Cannot write class of NULL concept!" << endl; return; } TQString classname = cleanName(c->getName()); //find an appropriate name for our file TQString fileName = findFileName(c, ".cs"); if (fileName.isEmpty()) { emit codeGenerated(c, false); return; } TQFile filecs; if (!openFile(filecs, fileName)) { emit codeGenerated(c, false); return; } TQTextStream cs(&filecs); ////////////////////////////// //Start generating the code!! ///////////////////////////// //try to find a heading file (license, coments, etc) TQString str; str = getHeadingFile(".cs"); if (!str.isEmpty()) { str.replace(TQRegExp("%filename%"),fileName); str.replace(TQRegExp("%filepath%"),filecs.name()); cs<getDocument(); UMLFolder *logicalView = umldoc->getRootFolder(Uml::mt_Logical); // write generic includes cs << "using System;" << m_endl; cs << "using System.Text;" << m_endl; cs << "using System.Collections;" << m_endl; cs << "using System.Collections.Generic;" << m_endl << m_endl; //write includes and namespace UMLPackage *container = c->getUMLPackage(); if (container == logicalView) container = NULL; UMLPackageList includes; findObjectsRelated(c, includes); m_seenIncludes.clear(); //m_seenIncludes.append(logicalView); if (includes.count()) { UMLPackage *p; for (UMLPackageListIt it(includes); (p = it.current()) != NULL; ++it) { UMLClassifier *cl = dynamic_cast(p); if (cl) p = cl->getUMLPackage(); if (p != logicalView && m_seenIncludes.findRef(p) == -1 && p != container) { cs << "using " << p->getFullyQualifiedName(".") << ";" << m_endl; m_seenIncludes.append(p); } } cs << m_endl; } m_container_indent = ""; if (container) { cs << "namespace " << container->getFullyQualifiedName(".") << m_endl; cs << "{" << m_endl << m_endl; m_container_indent = m_indentation; m_seenIncludes.append(container); } //Write class Documentation if there is somthing or if force option if (forceDoc() || !c->getDoc().isEmpty()) { cs << m_container_indent << "/// " << m_endl; cs << formatDoc(c->getDoc(), m_container_indent + "/// " ); cs << m_container_indent << "/// " << m_endl ; } UMLClassifierList superclasses = c->getSuperClasses(); UMLAssociationList aggregations = c->getAggregations(); UMLAssociationList compositions = c->getCompositions(); UMLAssociationList realizations = c->getRealizations(); bool isInterface = c->isInterface(); m_unnamedRoles = 1; cs << m_container_indent << "public "; //check if it is an interface or regular class if (isInterface) { cs << "interface " << classname; } else { //check if class is abstract and / or has abstract methods if (c->getAbstract() || c->hasAbstractOps()) cs << "abstract "; cs << "class " << classname << (superclasses.count() > 0 ? " : ":""); // write baseclass, ignore interfaces, write error on multiple inheritance if (superclasses.count() > 0) { UMLClassifier *obj; int supers = 0; for (obj = superclasses.first(); obj; obj = superclasses.next()) { if (!obj->isInterface()) { if (supers > 0) { cs << " // AND "; } cs << cleanName(obj->getName()); supers++; } } if (supers > 1) { cs << m_endl << "//WARNING: C# does not support multiple inheritance but there is more than 1 superclass defined in your UML model!" << m_endl; } } //check for realizations UMLAssociationList realizations = c->getRealizations(); UMLAssociation *a; if (!realizations.isEmpty()) { for (a = realizations.first(); a; a = realizations.next()) { UMLClassifier *real = (UMLClassifier*)a->getObject(Uml::B); if(real != c) { // write list of realizations cs << ", " << real->getName(); } } } } cs << m_endl << m_container_indent << '{' << m_endl; //associations if (forceSections() || !aggregations.isEmpty()) { cs << m_endl << m_container_indent << m_indentation << "#region Aggregations" << m_endl << m_endl; writeAssociatedAttributes(aggregations, c, cs); cs << m_endl << m_container_indent << m_indentation << "#endregion" << m_endl; } //compositions if (forceSections() || !compositions.isEmpty()) { cs << m_endl << m_container_indent << m_indentation << "#region Compositions" << m_endl << m_endl; writeAssociatedAttributes(compositions, c, cs); cs << m_endl << m_container_indent << m_indentation << "#endregion" << m_endl; } //attributes // FIXME: C# allows Properties in interface! if (!isInterface) writeAttributes(c, cs); //operations writeOperations(c, cs); //finish file cs << m_endl << m_container_indent << "}" << m_endl << m_endl; // close class if (container) { cs << "} // end of namespace " << container->getFullyQualifiedName(".") << m_endl << m_endl; } //close files and notfiy we are done filecs.close(); emit codeGenerated(c, true); } //////////////////////////////////////////////////////////////////////////////////// // Helper Methods void CSharpWriter::writeOperations(UMLClassifier *c, TQTextStream &cs) { //Lists to store operations sorted by scope UMLOperationList oppub,opprot,oppriv; bool isInterface = c->isInterface(); bool generateErrorStub = true; oppub.setAutoDelete(false); opprot.setAutoDelete(false); oppriv.setAutoDelete(false); //sort operations by scope first and see if there are abstract methods UMLOperationList opl(c->getOpList()); for (UMLOperation *op = opl.first(); op ; op = opl.next()) { switch (op->getVisibility()) { case Uml::Visibility::Public: oppub.append(op); break; case Uml::Visibility::Protected: opprot.append(op); break; case Uml::Visibility::Private: oppriv.append(op); break; default: break; } } // write realizations (recursive) UMLAssociationList realizations = c->getRealizations(); if (!isInterface && !realizations.isEmpty()) { writeRealizationsRecursive(c, &realizations, cs); } // write public operations if (forceSections() || !oppub.isEmpty()) { cs << m_endl << m_container_indent << m_indentation << "#region Public methods" << m_endl << m_endl; writeOperations(oppub,cs,isInterface,false,generateErrorStub); cs << m_container_indent << m_indentation << "#endregion" << m_endl << m_endl; } // write protected operations if (forceSections() || !opprot.isEmpty()) { cs << m_endl << m_container_indent << m_indentation << "#region Protected methods" << m_endl << m_endl; writeOperations(opprot,cs,isInterface,false,generateErrorStub); cs << m_container_indent << m_indentation << "#endregion" << m_endl << m_endl; } // write private operations if (forceSections() || !oppriv.isEmpty()) { cs << m_endl << m_container_indent << m_indentation << "#region Private methods" << m_endl << m_endl; writeOperations(oppriv,cs,isInterface,false,generateErrorStub); cs << m_container_indent << m_indentation << "#endregion" << m_endl << m_endl; } // write superclasses abstract methods UMLClassifierList superclasses = c->getSuperClasses(); if (!isInterface && !c->getAbstract() && !c->hasAbstractOps() && superclasses.count() > 0) { writeOverridesRecursive(&superclasses, cs); } } void CSharpWriter::writeOverridesRecursive(UMLClassifierList *superclasses, TQTextStream &cs) { // oplist for implemented abstract operations UMLOperationList opabstract; opabstract.setAutoDelete(false); UMLClassifier *obj; for (obj = superclasses->first(); obj; obj = superclasses->next()) { if (!obj->isInterface() && obj->hasAbstractOps()) { // collect abstract ops UMLOperationList opl(obj->getOpList()); for (UMLOperation *op = opl.first(); op ; op = opl.next()) { if (op->getAbstract()) { opabstract.append(op); } } // write abstract implementations cs << m_endl << m_container_indent << m_indentation << "#region " << obj->getName() << " members" << m_endl << m_endl; writeOperations(opabstract,cs,false,true,true); cs << m_container_indent << m_indentation << "#endregion" << m_endl << m_endl; opabstract.clear(); } // Recurse to parent superclasses UMLClassifierList superRecursive = obj->getSuperClasses(); UMLClassifierList *superRecursivePtr =& superRecursive; if (superRecursivePtr->count() > 0) { writeOverridesRecursive(superRecursivePtr, cs); } } } void CSharpWriter::writeRealizationsRecursive(UMLClassifier *currentClass, UMLAssociationList *realizations, TQTextStream &cs) { UMLAssociation *a; for (a = realizations->first(); a; a = realizations->next()) { // we know its a classifier if its in the list UMLClassifier *real = (UMLClassifier*)a->getObject(Uml::B); //FIXME: Interfaces realize themselves without this condition!? if (real == currentClass) continue; // collect operations of one realization UMLOperationList opreal = real->getOpList(); // write realizations cs << m_endl << m_container_indent << m_indentation << "#region " << real->getName() << " members" << m_endl << m_endl; writeOperations(opreal,cs,false,false,true); cs << m_container_indent << m_indentation << "#endregion" << m_endl << m_endl; // Recurse to parent realizations UMLAssociationList parentReal = real->getRealizations(); if (!parentReal.isEmpty()) { writeRealizationsRecursive(real, &parentReal, cs); } } } void CSharpWriter::writeOperations(UMLOperationList opList, TQTextStream &cs, bool isInterface /* = false */, bool isOverride /* = false */, bool generateErrorStub /* = false */) { for (UMLOperation *op=opList.first(); op ; op=opList.next()) { UMLAttributeList atl = op->getParmList(); UMLAttribute *at; //write method doc if we have doc || if at least one of the params has doc bool writeDoc = forceDoc() || !op->getDoc().isEmpty(); for (at = atl.first(); at; at = atl.next()) { writeDoc |= !at->getDoc().isEmpty(); } //write method documentation if (writeDoc && !isOverride) { cs << m_container_indent << m_indentation << "/// " << m_endl; cs << formatDoc(op->getDoc(), m_container_indent + m_indentation + "/// "); cs << m_container_indent << m_indentation << "/// " << m_endl; //write parameter documentation for (at = atl.first(); at; at = atl.next()) { if (forceDoc() || !at->getDoc().isEmpty()) { cs << m_container_indent << m_indentation << "/// getName()) << "\">"; //removing newlines from parameter doc cs << formatDoc(at->getDoc(), "").replace("\n", " ").remove('\r').replace(TQRegExp(" $"), ""); cs << "" << m_endl; } } // FIXME: "returns" should contain documentation, not type. cs << m_container_indent << m_indentation << "/// "; if (! op->getTypeName().isEmpty()) { cs << makeLocalTypeName(op); } cs << "" << m_endl; } // method visibility cs << m_container_indent << m_indentation; if (!isInterface) { if (!isOverride) { if (op->getAbstract()) cs << "abstract "; cs << op->getVisibility().toString() << " "; if (op->getStatic()) cs << "static "; } else { // method overriding an abstract parent cs << op->getVisibility().toString() << " override "; if (op->getStatic()) cs << "static "; } } // return type (unless constructor, destructor) if (!op->isLifeOperation()) { if (op->getTypeName().isEmpty()) { cs << "void "; } else { cs << makeLocalTypeName(op) << " "; } } // method name cs << cleanName(op->getName()) << "("; // method parameters int i= atl.count(); int j=0; for (at = atl.first(); at; at = atl.next(), j++) { cs << makeLocalTypeName(at) << " " << cleanName(at->getName()); // no initial values in C# //<< (!(at->getInitialValue().isEmpty()) ? // (TQString(" = ")+at->getInitialValue()) : // TQString("")) cs << ((j < i-1)?", ":""); } cs << ")"; //FIXME: how to control generation of error stub? if (!isInterface && (!op->getAbstract() || isOverride)) { cs << m_endl << m_container_indent << m_indentation << "{" << m_endl; if (generateErrorStub) { cs << m_container_indent << m_indentation << m_indentation; cs << "throw new Exception(\"The method or operation is not implemented.\");" << m_endl; } cs << m_container_indent << m_indentation << "}" << m_endl; } else { cs << ';' << m_endl; } cs << m_endl; } } void CSharpWriter::writeAttributes(UMLClassifier *c, TQTextStream &cs) { UMLAttributeList atpub, atprot, atpriv, atdefval; atpub.setAutoDelete(false); atprot.setAutoDelete(false); atpriv.setAutoDelete(false); atdefval.setAutoDelete(false); //sort attributes by scope and see if they have a default value UMLAttributeList atl = c->getAttributeList(); UMLAttribute *at; for (at = atl.first(); at ; at = atl.next()) { if (!at->getInitialValue().isEmpty()) atdefval.append(at); switch (at->getVisibility()) { case Uml::Visibility::Public: atpub.append(at); break; case Uml::Visibility::Protected: atprot.append(at); break; case Uml::Visibility::Private: atpriv.append(at); break; default: break; } } if (forceSections() || atl.count()) cs << m_endl << m_container_indent << m_indentation << "#region Attributes" << m_endl << m_endl; // write public attributes if (forceSections() || atpub.count()) { writeAttributes(atpub,cs); } // write protected attributes if (forceSections() || atprot.count()) { writeAttributes(atprot,cs); } // write private attributes if (forceSections() || atpriv.count()) { writeAttributes(atpriv,cs); } if (forceSections() || atl.count()) cs << m_endl << m_container_indent << m_indentation << "#endregion" << m_endl << m_endl; } void CSharpWriter::writeAttributes(UMLAttributeList &atList, TQTextStream &cs) { for (UMLAttribute *at = atList.first(); at ; at = atList.next()) { bool asProperty = true; if (at->getVisibility() == Uml::Visibility::Private) { asProperty = false; } writeAttribute(at->getDoc(), at->getVisibility(), at->getStatic(), makeLocalTypeName(at), at->getName(), at->getInitialValue(), asProperty, cs); cs << m_endl; } // end for return; } void CSharpWriter::writeAssociatedAttributes(UMLAssociationList &associated, UMLClassifier *c, TQTextStream &cs) { UMLAssociation *a; for (a = associated.first(); a ; a = associated.next()) { if (c != a->getObject(Uml::A)) // we need to be at the A side continue; UMLObject *o = a->getObject(Uml::B); if (o == NULL) { kError() << "composition role B object is NULL" << endl; continue; } // Take name and documentaton from Role, take type name from the referenced object TQString roleName = cleanName(a->getRoleName(Uml::B)); TQString typeName = cleanName(o->getName()); if (roleName.isEmpty()) { roleName = TQString("UnnamedRoleB_%1").arg(m_unnamedRoles++); } TQString roleDoc = a->getRoleDoc(Uml::B); //FIXME:is this simple condition enough? if (a->getMulti(Uml::B).isEmpty() || a->getMulti(Uml::B) == "1") { // normal attribute writeAttribute(roleDoc, a->getVisibility(Uml::B), false, typeName, roleName, "", ( a->getVisibility(Uml::B) != Uml::Visibility::Private), cs); } else { // array roleDoc += "\n(Array of " + typeName + ')'; writeAttribute(roleDoc, a->getVisibility(Uml::B), false, "ArrayList", roleName, "", ( a->getVisibility(Uml::B) != Uml::Visibility::Private), cs); } } } void CSharpWriter::writeAttribute(TQString doc, Uml::Visibility visibility, bool isStatic, TQString typeName, TQString name, TQString initialValue, bool asProperty, TQTextStream &cs) { if (forceDoc() || !doc.isEmpty()) { cs << m_container_indent << m_indentation << "/// " << m_endl; cs << formatDoc(doc, m_container_indent + m_indentation + "/// "); cs << m_container_indent << m_indentation << "/// " << m_endl; } cs << m_container_indent << m_indentation; cs << visibility.toString() << " "; if (isStatic) cs << "static "; //Variable type with/without namespace path cs << typeName << " "; cs << cleanName(name); // FIXME: may need a GUI switch to not generate as Property? // Generate as Property if not private if (asProperty) { cs << m_endl; cs << m_container_indent << m_indentation << "{" << m_endl; cs << m_container_indent << m_indentation << m_indentation << "get" << m_endl; cs << m_container_indent << m_indentation << m_indentation << "{" << m_endl; cs << m_container_indent << m_indentation << m_indentation << m_indentation << "return m_" << cleanName(name) << ";" << m_endl; cs << m_container_indent << m_indentation << m_indentation << "}" << m_endl; cs << m_container_indent << m_indentation << m_indentation << "set" << m_endl; cs << m_container_indent << m_indentation << m_indentation << "{" << m_endl; cs << m_container_indent << m_indentation << m_indentation << m_indentation << "m_" << cleanName(name) << " = value;" << m_endl; cs << m_container_indent << m_indentation << m_indentation << "}" << m_endl; cs << m_container_indent << m_indentation << "}" << m_endl; cs << m_container_indent << m_indentation << "private "; if (isStatic) cs << "static "; cs << typeName << " m_" << cleanName(name); } if (!initialValue.isEmpty()) cs << " = " << initialValue; cs << ";" << m_endl << m_endl; } TQString CSharpWriter::makeLocalTypeName(UMLClassifierListItem *cl) { UMLPackage *p = cl->getType()->getUMLPackage(); if (m_seenIncludes.findRef(p) != -1) { return cl->getType()->getName(); } else { return cl->getTypeName(); } } /** * returns "C#" */ Uml::Programming_Language CSharpWriter::getLanguage() { return Uml::pl_CSharp; } const TQStringList CSharpWriter::reservedKeywords() const { static TQStringList keywords; if (keywords.isEmpty()) { for (int i = 0; reserved_words[i]; i++) keywords.append(reserved_words[i]); } return keywords; } #include "csharpwriter.moc"