package pyUML.pythonTree; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.uml2.uml.Association; import org.eclipse.uml2.uml.Class; import org.eclipse.uml2.uml.Classifier; import org.eclipse.uml2.uml.Element; import org.eclipse.uml2.uml.Generalization; import org.eclipse.uml2.uml.Interface; import org.eclipse.uml2.uml.NamedElement; import org.eclipse.uml2.uml.Operation; import org.eclipse.uml2.uml.Property; import org.eclipse.uml2.uml.Realization; import org.eclipse.uml2.uml.Relationship; import org.eclipse.uml2.uml.Type; import org.eclipse.uml2.uml.VisibilityKind; import org.eclipse.uml2.uml.internal.impl.RealizationImpl; import org.python.pydev.parser.jython.ast.Assign; import org.python.pydev.parser.jython.ast.Attribute; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.jython.ast.Expr; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.Name; import org.python.pydev.parser.jython.ast.NameTok; import org.python.pydev.parser.jython.ast.Str; import org.python.pydev.parser.jython.ast.exprType; import org.python.pydev.parser.jython.ast.stmtType; import pyUML.backend.EclipseHelperMethods; import pyUML.backend.GlobalConstants; import pyUML.backend.ParseHelpers; import pyUML.exceptions.PyUMLCancelledException; import pyUML.exceptions.PyUMLParseException; import pyUML.exceptions.PyUMLSynchronizeCodeException; import pyUML.refactoring.BicycleRefactoring; import pyUML.refactoring.FileRefactoring; /** * Part of PythonTree representing a class * - can write XMI ID to classes docstring * */ public class PythonTreeClass extends PythonTreeNode{ ClassDef astNode; String docString; PythonTreeFile inFile; int docStringLine; int docStringCol; int lastLine; List usedStereotypes; Set superClasses; private String indent = " "; Map childMethodDict; Map childStaticAttrDict; Map childObjectAttrDict; /** * Constructor to create a PythonTreeClass of a python class AST * @param parent the PythonTreePackage, which is parent of this class * @param inFile the PythonTreeFile * @param astNode the corresponding Python AST node * @param lastLine the last line of this class definition; 0 if * class ends with end of file */ public PythonTreeClass(PythonTreeNode parent, PythonTreeFile inFile, ClassDef astNode, int lastLine) throws PyUMLParseException, PyUMLCancelledException{ super(parent); this.astNode = astNode; this.inFile = inFile; this.lastLine = lastLine; this.superClasses = new HashSet(); this.childMethodDict = new Hashtable(); this.childStaticAttrDict = new Hashtable(); this.childObjectAttrDict = new Hashtable(); this.initClassNode(); this.initSubStructures(); } private void initClassNode() throws PyUMLParseException, PyUMLCancelledException{ this.name=((NameTok) this.astNode.name).id; this.docStringLine = this.astNode.beginLine; this.docStringCol = this.astNode.beginColumn; // get class Body stmtType[] classBody = this.astNode.body; if (classBody.length == 0) { return; } // extract DocString, if it exists if (classBody[0] instanceof Expr) { Expr exp = (Expr) classBody[0]; if (exp.value instanceof Str) { Str stringExp = (Str) exp.value; if (stringExp.type == Str.TripleDouble) { this.docString=stringExp.s; this.docStringLine = stringExp.beginLine; this.docStringCol = stringExp.beginColumn; } } } //extract XMI-ID from DocString if (this.docString != null) { this.xmi_id = ParseHelpers.extractXmiFromString(this.docString); if (this.xmi_id != null) this.getRoot().getXmiClassDict().put(this.xmi_id, this); } PythonTreeRoot root = this.getRoot(); // test if a class of this name already exists String structuredName = this.getPackageStructure()+this.name; this.getRoot().setSubTaskName(structuredName); if (root.getClassDict().containsKey(structuredName) && root.isShowWarnings()) { String message = "Duplicate class detected!\n" + "More than one class with the name " + this.name + " was found \n" + "in the package "+this.getPackageStructure() +"." + "\nThis can lead to problems in PyUML.\n" + "Please rename one of the classes in the code!\n" + "Both classes will be ignored!"; this.getParent().getChildClasses().remove(this.getRoot().getClassDict().get(structuredName)); this.getRoot().getClassDict().remove(structuredName); this.getRoot().getClassNameDict().remove(structuredName); MessageDialog.openWarning(null, "Duplicate class detected", message); return; //throw new PyUMLParseException(message); } // initialize empty set of superclasses root.getClassDict().put(structuredName, this); root.getClassNameDict().put(structuredName, this.name); for (exprType superClass: this.astNode.bases) { if (superClass instanceof Name) { String superName = ((Name)superClass).id; String superClassPackString = this.getPackFromImports(superName); if (superClassPackString != null) this.superClasses.add(superClassPackString); } else if (superClass instanceof Attribute) { Attribute superAtt = (Attribute) superClass; String superName = ((NameTok)superAtt.attr).id; String packages = ((Name)superAtt.value).id; String packageStructure = "/" + packages.replace(".", "/") + "/"; this.superClasses.add(packageStructure+superName); } } } /** * initializes all Substructures of this class * - Methods (object or static) * - Static attributes (in class body) * - Object attributes (in __init(self)) * * The PythonTree child objects are created and initialized * in their constructor */ private void initSubStructures() throws PyUMLParseException{ // parse class body stmtType[] statements = this.astNode.body; for (int i=0; i 0) for (; line.charAt(indentLength) == ' ' && line.length() > indentLength +1; indentLength++) ; if (this.indent.length() != indentLength && ! line.matches("^[\\s]*\\\"\\\"\\\".*$")) { this.indent = ""; for (int j=0; j< indentLength; j++) this.indent += " "; } // get functions and object attributes int nextLine; if (i < statements.length -1) nextLine = statements[i+1].beginLine; else nextLine = (this.lastLine == -1) ? -1 : this.lastLine + 1; if (statement instanceof FunctionDef) { // get line of next statement -> for a static method, this should // be the staticmethod call PythonTreeMethod childMethod = new PythonTreeMethod(this, (FunctionDef) statement, nextLine); this.childMethodDict.put(childMethod.getName(), childMethod); // look for object attributes in __init__ if (childMethod.getName().equals("__init__")) { stmtType[] methodStmts = childMethod.getAstNode().body; // iterate over all method body statements for (int j=0; j < methodStmts.length; j++) { int nextStmtLine = nextLine; if (j < methodStmts.length - 1 ) nextStmtLine = methodStmts[j+1].beginLine; stmtType methodStmt = methodStmts[j]; if (methodStmt instanceof Assign) { // need line of next statement to be able // to multi-line-expressions if (j < methodStmts.length-1) nextStmtLine = methodStmts[j+1].beginLine; PythonTreeAttribute childAtt = new PythonTreeAttribute(this, (Assign) methodStmt, false, nextStmtLine); if (childAtt.isValid()) this.childObjectAttrDict.put(childAtt.getName(), childAtt); } } } } // get static attributes else if (statement instanceof Assign) { PythonTreeAttribute childAtt = new PythonTreeAttribute(this, (Assign) statement, true, nextLine); if (childAtt.isValid()) this.childStaticAttrDict.put(childAtt.getName(), childAtt); } } } /** * Write/update the given XMI id to the classes docstring * @param xmiID */ public void writeXmiID(String xmiID) { if (this.docString != null) { this.docString = docString.replaceAll("\\n[\\s]*# PyUML: [^\\n]*", ""); String lineToInsert =""; lineToInsert += "# PyUML: Do not remove this line! # XMI_ID:" + xmiID+"\n"; if (! this.docString.endsWith("\n")) lineToInsert = "\n"+this.indent + lineToInsert + this.indent ; if (! this.docString.startsWith("\n")) this.docString = "\n"+this.indent + this.docString; this.docString = docString + lineToInsert; FileRefactoring.replaceFromCoordinate(this.inFile, this.docStringLine, this.docStringCol+3, this.docString, "\"\"\"", true); }else{ this.docString=this.indent+"\"\"\" # PyUML: Do not remove this line! # XMI_ID:" + xmiID + "\"\"\"\n"; FileRefactoring.insertAtLine(this.inFile, this.docStringLine + 1, this.docString); } } /** * Update the associations in the classes docstring. */ public boolean updateAssociations(Classifier modelClass) throws PyUMLSynchronizeCodeException { if (this.docString != null) { String lineToInsert=""; if (modelClass.getAssociations().size() > 0) lineToInsert += "# PyUML: Associations of this class:\n"; associationsLoop: for (Association as : modelClass.getAssociations()) { String asName = ""; if (as.getName() != null && as.getName().length() > 0 ) asName = "'" + as.getName()+"' "; List ends = as.getEndTypes(); String otherClass = "self "; String otherEndLabel =""; String ownEndLabel =""; if (as.getMemberEnds().size() >1) { Type end; if (ends.size() > 1) { Type end1 = ends.get(0); Type end2 = ends.get(1); end = (end1.equals(modelClass) ? end2 : end1); otherEndLabel = (end1.equals(modelClass) ? as.getMemberEnds().get(1).getName() : as.getMemberEnds().get(0).getName()); ownEndLabel = (! end1.equals(modelClass) ? as.getMemberEnds().get(1).getName() : as.getMemberEnds().get(0).getName()); } else { Type end1 = ends.get(0);; Type end2 = as.getMemberEnds().get(1).getType(); if (end2 == null) end = end1; else end = (end1.equals(modelClass)?end2:end1); otherEndLabel = as.getMemberEnds().get(0).getName(); ownEndLabel = as.getMemberEnds().get(1).getName(); if (end.equals(modelClass)) { // if the only known end of the navigatable association // is this class, ignore this association. continue associationsLoop; } } if (otherEndLabel != null && otherEndLabel.length() > 0) otherEndLabel = " (being '"+otherEndLabel + "')"; else otherEndLabel = ""; if (ownEndLabel != null && ownEndLabel.length() > 0) ownEndLabel = "(being '"+ownEndLabel + "') "; else ownEndLabel = ""; String endType = ""; if (end instanceof Classifier) endType = "class"; else if (end instanceof Interface) endType = "interface"; else endType = "model element"; otherClass = endType+" "+ end.getName()+otherEndLabel+" in package "+ParseHelpers.getModelPackageStructure(end)+" "; } lineToInsert += this.indent+"# PyUML: Association "+asName+ownEndLabel+"to " +otherClass+"\n"; } String newDocString = this.docString.replaceAll("\\n[\\s]*# PyUML: Association[^\\n]*", ""); if (! newDocString.endsWith("\n" + this.indent)) lineToInsert = "\n" + this.indent + lineToInsert; if (! newDocString.startsWith("\n")) newDocString = "\n" + this.indent + newDocString; newDocString += lineToInsert; if (newDocString.equals(this.docString)) return false; this.docString = newDocString; FileRefactoring.replaceFromCoordinate(this.inFile, this.docStringLine, this.docStringCol+3, this.docString, "\"\"\"", true); this.setChanged(this.getPackageStructure()+this.getName()+"-docString", 0); return true; } return false; } /** * gets a Set of PythonTreeClasses that are * the superclasses of this class, which are located * in the same project. * This must be called only after a complete initialization * of the Python Tree so that all super classes can be found * in the root node's classNameDict. */ public Set getGeneralizationsInProject() { // try to find PythonTreeClasses from the superclass Strings Set superClassesInProject = new HashSet(); for (String superClass :this.superClasses) { if (this.getRoot().getClassDict().containsKey(superClass)) superClassesInProject.add(this.getRoot().getClassDict().get(superClass)); } return superClassesInProject; } /** * gets a Set of PythonTreeClasses that are * the superclasses of this class, which are located * in the same project. -> Uses the model generalizations to find * the general class, not the code. * This must be called only after a complete initialization * of the Python Tree so that all super classes can be found * in the root node's classNameDict. */ public Set getModelGeneralizationsInProject(Classifier modelClass) { Set superClassesInProject = new HashSet(); for (Relationship rel : modelClass.getRelationships()) { if (rel instanceof Realization) { Realization real = (Realization) rel; if (real.getClients().size() > 0 && real.getClients().get(0) instanceof Classifier) { Classifier client = (Classifier) real.getClients().get(0); String packageStructure = ParseHelpers.getModelPackageStructure(client) + client.getName(); PythonTreeClass pyClass = this.getRoot().getClassDict().get(packageStructure); superClassesInProject.add(pyClass); } } } for (Classifier superClass : modelClass.getGenerals()) { String packageStructure = ParseHelpers.getModelPackageStructure(superClass) + superClass.getName(); PythonTreeClass pyClass = this.getRoot().getClassDict().get(packageStructure); if (pyClass != null) superClassesInProject.add(pyClass); } return superClassesInProject; } /** * synchronize methods and attributes of the model with this class * This is not done by xmi_id, but elements that changed name are always * deleted and newly created */ public boolean synchronizeModel(NamedElement modelElement){ // run super method to save corresponding model element super.synchronizeModel(modelElement); Classifier modelClass = (Classifier) modelElement; // set this class private in model, if its name starts with "__" // use protected, if class starts with "_" (this is only python convention) if (this.getName().startsWith("__")) { modelClass.setVisibility(VisibilityKind.PRIVATE_LITERAL); } else if (this.getName().startsWith("_")) { modelClass.setVisibility(VisibilityKind.PROTECTED_LITERAL); } // ## process methods and attributes of this class ## // create dictionary of methods / classes, so that // a model element can be accessed by name Map modelChildMethods = new Hashtable(); EList ownedOperations = null; if (modelClass instanceof Class) ownedOperations = ((Class) modelClass).getOwnedOperations(); else if (modelClass instanceof Interface) ownedOperations = ((Interface) modelClass).getOwnedOperations(); else ownedOperations = new BasicEList(); for (Operation op : ownedOperations) { modelChildMethods.put(op.getName(), op); } List processedOperations = new Vector(); Map modelChildProps = new Hashtable(); for (Property prop : modelClass.getAttributes()) { modelChildProps.put(prop.getName(), prop); } List processedProperties = new Vector(); // recurse on methods / attributes, if they exist in model; // otherwise create and recurse for (PythonTreeMethod pyChildMeth : this.childMethodDict.values()) { if (modelChildMethods.containsKey(pyChildMeth.getName())){ // everything is OK, child method with same name found. // method parameters are edited in child element // ### Synchronize child ### boolean success = pyChildMeth.synchronizeModel(modelChildMethods.get(pyChildMeth.getName())); if (! success) return false; processedOperations.add(modelChildMethods.get(pyChildMeth.getName())); } else { // Method does *not* exist -> create it and process children Operation modelNewMethod = null; if (modelClass instanceof Class) modelNewMethod = ((Class) modelClass).createOwnedOperation(pyChildMeth.getName(), null, null); else if (modelClass instanceof Interface) modelNewMethod = ((Interface) modelClass).createOwnedOperation(pyChildMeth.getName(), null, null); // ### Synchronize child ### boolean success = pyChildMeth.synchronizeModel(modelNewMethod); if (! success) return false; processedOperations.add(modelNewMethod); } } Collection allAttributes = new Vector(); allAttributes.addAll(this.childStaticAttrDict.values()); allAttributes.addAll(this.childObjectAttrDict.values()); for (PythonTreeAttribute pyChildAtt : allAttributes) { if (modelChildProps.containsKey(pyChildAtt.getName())){ // everything is OK, child method with same name found. // method parameters are edited in child element // ### Synchronize child ### boolean success = pyChildAtt.synchronizeModel(modelChildProps.get(pyChildAtt.getName())); if (! success) return false; processedProperties.add(modelChildProps.get(pyChildAtt.getName())); } else { // Attribute does *not* exist -> create it and process children Property modelNewProp = null; if (modelClass instanceof Class) modelNewProp = ((Class) modelClass).createOwnedAttribute(pyChildAtt.getName(), null); else if (modelClass instanceof Interface) modelNewProp = ((Interface) modelClass).createOwnedAttribute(pyChildAtt.getName(), null); // ### Synchronize child ### boolean success = pyChildAtt.synchronizeModel(modelNewProp); if (! success) return false; processedProperties.add(modelNewProp); } } // delete all methods / attributes, that do no longer exist in code for (Operation op : modelClass.getOperations()) { if (! processedOperations.contains(op)) this.getRoot().getModelElementsToDestroy().add(op); } for (Property prop : modelClass.getAttributes()) { if (! processedProperties.contains(prop)) this.getRoot().getModelElementsToDestroy().add(prop); } return true; } /////// Synchronize Code (see superclass) /////// public boolean synchronizeCode(Classifier modelClass) throws PyUMLSynchronizeCodeException{ // get and filter Stereotypes of this model class // (only PyUML Stereotypes, as defined in GlobalConstants, are saved) this.usedStereotypes = new Vector(); List stypes = modelClass.getStereotypeApplications(); for (EObject stObject : stypes) { String stereotypeName = stObject.eClass().getName(); if (GlobalConstants.getPossibleStereoTypes().contains(stereotypeName)) { this.usedStereotypes.add(stereotypeName); } } this.handleStereoTypes(modelClass); // see if class was renamed: if (! this.getName().equals(modelClass.getName())) { // now, the file name is OK, rename the class in python code this.renameClassInCode(modelClass); this.getRoot().getRenamedClasses().add(this.getName()); return true; } // if class was renamed, and the old class name matches the file name, // rename the file // TODO: Works and can be uncommented, // but generalizations/imports are not // updated, so leaving that commented /* if (this.getRoot().getRenamedClasses().contains(this.inFile.getFilePath().lastSegment().replace(".py", ""))) { IPath newFilePath = this.renameClassFile(modelClass.getName()); if (newFilePath != null) { this.inFile.setFilePath(newFilePath.addFileExtension("py")); return true; } else throw new PyUMLSynchronizeCodeException(); } */ // see if list of superclasses changed boolean superClassesChanged = false; List interfaceList = new Vector(); for (Relationship rel : modelClass.getRelationships()) { if (rel instanceof Realization) { interfaceList.add((Realization) rel); } } if (modelClass.getGeneralizations().size() + interfaceList.size() != this.superClasses.size()) superClassesChanged =true; else for (Generalization gen : modelClass.getGeneralizations()) { if (! this.superClasses.contains( ParseHelpers.getModelPackageStructure(gen.getGeneral()) + gen.getGeneral().getName())) { superClassesChanged = true; } } // if yes, rewrite list of generalizations if (superClassesChanged) this.rewriteGeneralizations(modelClass, interfaceList); // if there are non-static parameters, make sure there is a // __init__-method in the class boolean hasStaticAttributes = false; boolean hasNonStaticAttributes = false; EList ownedProperties = null; if (modelClass instanceof Class) ownedProperties = ((Class) modelClass).getOwnedAttributes(); else if (modelClass instanceof Interface) ownedProperties = ((Interface) modelClass).getOwnedAttributes(); for (Property modelProp : ownedProperties) { if (! modelProp.isStatic()) hasNonStaticAttributes = true; else hasStaticAttributes = true; } if (hasNonStaticAttributes && (! childMethodDict.containsKey("__init__"))) { this.createNewMethod("__init__", this.lastLine, false, null); this.getRoot().setChangedFileLine("def __init__(self):"+this.lastLine); return true; } if (hasNonStaticAttributes) { // check the __init__ method; this is not necessarily checked automatically, as // it might be not present in the model boolean changed = PythonTreeMethod.fixAttributeDefinitions(modelClass, childMethodDict.get("__init__").getIndent(), this.childObjectAttrDict, this, childMethodDict.get("__init__").getLastLine(), false); if (changed) return true; } if (hasStaticAttributes) { // check if all static attributes in model are present in code int insertLine; if (this.astNode.body.length > 0) insertLine = this.astNode.body[0].beginLine + 1; else insertLine = this.lastLine; boolean changed = PythonTreeMethod.fixAttributeDefinitions(modelClass, " ", this.childStaticAttrDict, this, insertLine, true); if (changed) return true; } // iterate over child elements of model // save all method names in iteration to detect duplicate method names List childModelOpNames = new Vector(); for (Element modelChild:modelClass.getOwnedElements()) { //child method found if (modelChild instanceof Operation) { Operation childModelOp = (Operation) modelChild; // test if a method with this name already existed in model. // if yes, an exception is thrown -> overloading is not supported in Python if (childModelOpNames.contains(childModelOp.getName())) { throw new PyUMLSynchronizeCodeException("Multiple methods with same name are not\n" + "supported in Python!\n" + "Please correct this error in the UML model!\n" + "Source:\n" + "- Method: "+ childModelOp.getName() + "\n- Class: "+ this.getName() + "\n- Package: "+this.getPackageStructure()); } else { childModelOpNames.add(childModelOp.getName()); } PythonTreeMethod childPyOp = null; for (PythonTreeMethod childMeth : this.childMethodDict.values()) { if (childMeth.getName().equals(childModelOp.getName())) { childPyOp = childMeth; } } if (childPyOp != null) { // everything OK, child method with same name found boolean startNew = childPyOp.synchronizeCode(childModelOp); if (startNew) return true; } else { // look if method was existing and was renamed String xmi_id = this.getRoot().getModelXmiDict().get(childModelOp); Map xmiModelDictOld = this.getRoot().getXmiModelDictOld(); if (xmiModelDictOld.containsKey(xmi_id)) { Operation oldOp = (Operation) xmiModelDictOld.get(xmi_id); String oldOpClassName = ((NamedElement)oldOp.getOwner()).getName(); if (oldOpClassName.equals(this.getName())) { String oldName = oldOp.getName(); PythonTreeMethod renamedPyOp = null; for (PythonTreeMethod childMeth : this.childMethodDict.values()) { if (childMeth.getName().equals(oldName)) { renamedPyOp = childMeth; } } if (renamedPyOp != null) { // found operation that was renamed. // rename it also in code int line = renamedPyOp.getAstNode().beginLine; int col = renamedPyOp.getAstNode().name.beginColumn; boolean result = BicycleRefactoring.doRenameObject(this.inFile, childModelOp.getName(), line, col, this.getRoot().getProject()); if (! result) return false; setChanged(childModelOp.getName(), line); return true; } // else -> was not renamed -> create new method this.createNewMethod(childModelOp.getName(), this.lastLine, childModelOp.isStatic(), childModelOp); setChanged(childModelOp.getName(), this.lastLine); return true; } // else -> was not renamed -> create new method this.createNewMethod(childModelOp.getName(),this.lastLine, childModelOp.isStatic(), childModelOp); setChanged(childModelOp.getName(), this.lastLine); return true; } // else -> was not renamed -> create new method this.createNewMethod(childModelOp.getName(),this.lastLine, childModelOp.isStatic(), childModelOp); setChanged(childModelOp.getName(), this.lastLine); return true; } } } // check if imports are correct! boolean changed= this.fixImports(modelClass); if (changed) return true; changed= this.updateAssociations(modelClass); if (changed) return true; return false; } /** * Realize all recognized stereotypes; * For the BeanClass Stereotype, this will simply create getter/setter * methods for every non-static attribute in the model, which will then automatically * be realized as code. */ public void handleStereoTypes(Classifier modelClassifier) { if (! (modelClassifier instanceof Class)) { return; } Class modelClass = (Class) modelClassifier; if (this.usedStereotypes.contains(GlobalConstants.STEREOTYPE_BEANCLASS)) { for (Property att : modelClass.getOwnedAttributes()) { String attName = att.getName(); // make first letter uppercase for CamelCase bean standard attName = attName.substring(0,1).toUpperCase() + attName.substring(1); // add getter/setter // check if method already exists boolean getterExists = false; boolean setterExists = false; for (Operation op : modelClass.getOwnedOperations()) { if (op.getName().equals("get"+attName)) getterExists = true; if (op.getName().equals("set"+attName)) setterExists = true; } if (!getterExists) { Operation op = modelClass.createOwnedOperation("get"+attName, null, null); op.createOwnedParameter("self", null); op.createOwnedComment().setBody("Getter Created by PyUML:"+att.getName()); } if (!setterExists) { Operation op = modelClass.createOwnedOperation("set"+attName, null, null); op.createOwnedParameter("self", null); op.createOwnedParameter("value", null); op.createOwnedComment().setBody("Setter Created by PyUML:"+att.getName()); } } } } /** * rename classes using bicycle repair man * @param modelClass * @return success */ public boolean renameClassInCode(Classifier modelClass) throws PyUMLSynchronizeCodeException{ // rename Class using Bicycle Repair Man return BicycleRefactoring.doRenameObject( this.inFile, modelClass.getName(), this.astNode.name.beginLine, this.astNode.name.beginColumn, this.getRoot().getProject()); } /** * Move this class in the python code to another package/folder * * Rename the class file * * @param newPath the IPath where the class is to be moved * @return true on success, false if package already exists */ public boolean moveClass(IPath newPath) { IFile classFile = EclipseHelperMethods.createFile( this.inFile.getFilePath().toOSString(), this.getProject()); if (! classFile.exists()) { MessageDialog.openError(null, "Error moving class", "Class " + this.name + " cannot " + "be renamed\n because it does not seem to exist!"); return false; } try { newPath = EclipseHelperMethods.createFile(newPath.toOSString()+".py", this.getProject()).getFullPath(); classFile.move(newPath, true, null); this.inFile.setFilePath(this.getProject().getLocation().append(newPath)); } catch (CoreException e) { MessageDialog.openError(null, "Error moving class", "The class " +this.name+ " cannot" + " be moved to "+ newPath +".\n Propably a class with the new name already exists!" + "\n\nReason:\n" +e.getMessage()); return false; } PythonTreePackage.completeNewStart = true; return true; } /** * Rename this class file in the python code * * @param newName the new name of the class in the same directory * @return the iPath of the created file, null if new class file already exists */ public IPath renameClassFile(String newName) { IFile classFile = EclipseHelperMethods.createFile(this.inFile.getFilePath().toOSString(), this.getProject()); IPath newPath = classFile.getLocation().removeLastSegments(1).append(newName); if (this.moveClass(newPath)) return newPath; else return null; } /** * Add/remove generalizations from class in code * @param modelClass * @return success */ public boolean rewriteGeneralizations(Classifier modelClass, List interfaceList) { int line = this.astNode.beginLine; String superClassString = ""; // write all available generalizations as a comma-separated string for (Generalization gen : modelClass.getGeneralizations()) { if (superClassString.length() > 0) superClassString +=", "; superClassString+= gen.getGeneral().getName(); } for (Realization real :interfaceList) { if (superClassString.length() > 0) superClassString +=", "; superClassString+= real.getClients().get(0).getName(); } // write all generalizations that were in code, but are not available // in model (e.g. system classes) for (String gen : this.superClasses) { // if this class is part of project, is should be in model // -> if it is not in model, it is to be removed if (this.getRoot().getClassNameDict().containsValue( gen.substring(gen.lastIndexOf("/")+1))) continue; // otherwise, this generalization has to stay, because it cannot be // present in model if (superClassString.length() > 0) superClassString +=", "; superClassString+= gen.substring(gen.lastIndexOf("/")+1); } // if there were generalizations, surround them by parenthesis if (superClassString.length() > 0) { superClassString = "(" + superClassString + ")"; } // replace generalizations in class definition String classDefLine = FileRefactoring.getLine(this.inFile, line); Pattern pattern = Pattern.compile("([\\s]*class[\\s]*[^:\\(]*[\\s]*)(\\(?[^:]*):"); Matcher matcher = pattern.matcher(classDefLine); if (! matcher.find()){ return false; } String newClassDefLine = matcher.group(1) + superClassString + ":"; if (! classDefLine.equals(newClassDefLine)) FileRefactoring.replaceLine(this.inFile, line, newClassDefLine, true); return true; } /** * create a new class in a File with the classes name * use all information of the model class * * @param modelClass * @param parentPackage * @return true on success, false if something went wrong */ public static boolean createNewClass(Classifier modelClass, PythonTreePackage parentPackage) throws PyUMLSynchronizeCodeException{ String className = modelClass.getName(); File newClassFile = new File(parentPackage.getPackageDir().append(className).toOSString() + ".py"); // create class definition line to insert to file String newClassLine = ""; newClassLine += "class " + className; // create file if (!newClassFile.exists()) { try { newClassFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); throw new PyUMLSynchronizeCodeException(); } } else { // if file already exists, append new content newClassLine = ParseHelpers.fileToString(newClassFile, parentPackage.getProject()) + "\n" + newClassLine; } // get all superclasses and add them to class definition EList superClasses = modelClass.getGenerals(); if (superClasses.size() > 0) { newClassLine = "\n" + newClassLine; newClassLine +="("; for (int i=0; i 0) newClassLine += ", "; newClassLine += superClass.getName(); } newClassLine += ")"; } newClassLine += ":"; String xmi_id= parentPackage.getRoot().getModelXmiDict().get(modelClass); if (modelClass instanceof Class) newClassLine += "\n \"\"\"\n Class created by PyUML"; else newClassLine += "\n \"\"\"\n Interface created by PyUML"; newClassLine += "\n # PyUML: Do not remove this line! # XMI_ID:" + xmi_id + "\n \"\"\""; ParseHelpers.stringToFile(newClassFile, newClassLine, parentPackage.getProject()); parentPackage.getRoot().setChangedFileLine(newClassFile.getAbsolutePath()); return true; } /** * Checks the current import statements to ensure all superclasses * are imported * * @return true, if anything was changed, false otherwise */ public boolean fixImports(Classifier modelClass) throws PyUMLSynchronizeCodeException{ // iterate over all superclasses in project Map superClassPackages = new HashMap(); for (PythonTreeClass superClass : this.getModelGeneralizationsInProject(modelClass)) { // get package structure, e.g. mypackage.subpackage.MyClass String packageDef = ""; PythonTreePackage superPackage = superClass.getParent(); while (superPackage != null && (! (superPackage instanceof PythonTreeRoot))) { packageDef = superPackage.getName() + "." + packageDef; superPackage = superPackage.getParent(); } String superClassModuleName = superClass.inFile.getName().replace(".py", ""); packageDef += superClassModuleName; superClassPackages.put(superClass.getName(), packageDef); } // find import statements in code // suppose, that all import statements are at the beginning of the // file, only white or comment lines can be above them. String[] fileContent = this.inFile.getFileContent().split("\n"); int lineNo = 0; String line = ""; if (superClassPackages.size() > 0) { do { line = fileContent[lineNo]; Pattern pattern = Pattern.compile("[\\s]*from[\\s]*([^\\s]*)[\\s]*import[\\s]*([^\\s#]*)([\\s#]?.*)"); Matcher matcher = pattern.matcher(line); if (matcher.find()){ String className = matcher.group(2); String packageDef = matcher.group(1); // if class is imported, but package changed -> rewrite line! if (superClassPackages.containsKey(className) && ( ! packageDef.equals(superClassPackages.get(className)))) { String newString = "from " + superClassPackages.get(className) + " import " + className + matcher.group(3); FileRefactoring.replaceLine(this.inFile, lineNo, newString, true); this.setChanged(null, lineNo); return true; } if (superClassPackages.containsKey(className) && superClassPackages.get(className).equals(packageDef)) { // line is OK -> remove them from new-import-to-insert-list superClassPackages.remove(className); } } lineNo ++; } while(line.matches("[\\s]*[#]?") || line.matches("from.*import.*")||line.matches("import.*")); // now, all lines were analyzed; the needed import, that were not covered // by a line, must be inserted now! if (superClassPackages.size() == 0) return false; String insertLines = ""; for (String superClassName : superClassPackages.keySet()) { String packageLine = superClassPackages.get(superClassName); insertLines += "from " + packageLine + " import " + superClassName + "\n"; } // if last line is empty, don't insert new empty line if (lineNo == 1) // no empty lines are in fron of the insertion -> insert empty line insertLines += "\n"; else if (line.length() > 0 && (! fileContent[lineNo-2].matches("^[\\s]*$"))) { insertLines += "\n"; } FileRefactoring.insertAtLine(this.inFile, lineNo-1, insertLines); this.setChanged(null, lineNo); return true; } return false; } /** * This creates a new method in the current class * * @param name the method's name * @param lastClassLine the last line in code that is part of the class * (or the first line after class -1) * -> this is the line where the method is inserted! * can be -1 if the method should be appended to end of file * @param isStatic if true, method will have a staticmethod call * @param modelOp the corresponding model operation; if there is a comment * indicating that this is a getter/setter, the appropriate method content * is created. Can be null. */ public void createNewMethod(String name, int lastClassLine, boolean isStatic, Operation modelOp) { String newLine; if (isStatic) { newLine = "\n" + this.indent + "def " + name +"():\n \"\"\" Created by PyUML \"\"\"\n" + this.indent + name + " = staticmethod (" + name + ")\n"; } else { newLine = "\n" + this.indent + "def " + name +"(self):\n \"\"\" Created by PyUML \"\"\"\n"; } // If this is a getter/setter class, put content to method if (modelOp != null && modelOp.getOwnedComments().size() > 0) { String opComment = modelOp.getOwnedComments().get(0).getBody(); if (opComment.startsWith("Getter Created by PyUML:")) { String attribute = opComment.replaceAll("Getter Created by PyUML:", ""); newLine += " return self."+attribute+"\n"; } else if (opComment.startsWith("Setter Created by PyUML:")) { String attribute = opComment.replaceAll("Setter Created by PyUML:", ""); newLine += " self."+attribute+" = value\n"; } } if (lastClassLine <= 0) FileRefactoring.appendToFile(this.inFile, newLine); else FileRefactoring.insertAtLine(this.inFile, lastClassLine, newLine); } /** * returns the parent package of this class */ public PythonTreePackage getParent() { return this.inFile.getParent(); } /** * tells the position of current code change to PythonTreeRoot * so that it can decide if the same line is changed again * and again and thus detect an unfinished loop. * * @param pos Additional information. This is useful, if * line is -1 (append), so that only dead-loops are detected * @param line the changed line in the file */ private void setChanged(String pos, int line) throws PyUMLSynchronizeCodeException{ if (pos == null) pos = ""; this.getRoot().setChangedFileLine(inFile.getFilePath().toOSString()+":"+line+":"+pos); } public Map getChildMethodDict() { return childMethodDict; } public Map getChildStaticAttrDict() { return childStaticAttrDict; } public Map getChildObjectAttrDict() { return childObjectAttrDict; } public void setChildStaticAttrDict( Map childStaticAttrDict) { this.childStaticAttrDict = childStaticAttrDict; } /** * This creates a string containing the python package structure as * "/packSup/packSub/" , where the last package contains the * current class. * @return a string containing the parent package structure * of this class */ public String getPackageStructure() { PythonTreePackage pack = this.getParent(); String structure = ""; while (! (pack instanceof PythonTreeRoot)) { structure = "/"+pack.getName() + structure ; pack = pack.getParent(); } return structure + "/"; } /** * Gets a complete package structure (including class) * from the class imports for a class (separated by "/" * e.g. "a/b/c/MyClass" for "MyClass", if there is * an import line "from a.b.c.MyClassFile import MyClass" * @param className the name of the class * @return the full package/class structure */ public String getPackFromImports(String className) { String fileContent = this.getInFile().getFileContent(); Pattern pattern = Pattern.compile(".*from[\\s]+([^\\s]+)\\.[^\\.\\s]+[\\s]+import[\\s]+"+className+".*"); Matcher matcher = pattern.matcher(fileContent); // if no import (with class structure in front) was found, we assume that the class // is in the same package if (! matcher.find()){ return this.getPackageStructure() + className; } String result = "/" + (matcher.group(1) + "." + className).replace(".", "/"); return result; } /** * @return the file where this class is defined */ public PythonTreeFile getInFile() { return inFile; } public List getUsedStereotypes() { return usedStereotypes; } public String getIndent() { return indent; } }