package pyUML.pythonTree; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Vector; import org.eclipse.core.resources.IFolder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.uml2.uml.Class; import org.eclipse.uml2.uml.Classifier; import org.eclipse.uml2.uml.Element; import org.eclipse.uml2.uml.Interface; import org.eclipse.uml2.uml.NamedElement; import org.eclipse.uml2.uml.Package; import pyUML.backend.EclipseHelperMethods; import pyUML.backend.ParseHelpers; import pyUML.exceptions.PyUMLCancelledException; import pyUML.exceptions.PyUMLParseException; import pyUML.exceptions.PyUMLSynchronizeCodeException; /** * This class represents a package directory in the python * code structure. * A package can contain child packages or Python files. */ public class PythonTreePackage extends PythonTreeNode{ IPath packageDir; private List childPackages; private List childClasses; private List pythonFiles; private PythonTreeFile initPy; static int count = 0; // this is used when a class was moved -> a complete restart is needed // instead of re-initializing the changed class static boolean completeNewStart = false; public PythonTreePackage(IPath packageDir, PythonTreeNode parent) throws PyUMLParseException, PyUMLCancelledException { super(parent); this.packageDir = packageDir; this.childClasses = new Vector(); this.childPackages = new Vector(); this.pythonFiles = new Vector(); if (packageDir != null) { initPackage(); } } private void initPackage() throws PyUMLParseException, PyUMLCancelledException{ File dir = new File(packageDir.toOSString()); this.name = dir.getName(); // list dir File[] dirList = dir.listFiles(); for (File entry:dirList) { // if directory found: check if it is package, // if yes, recursively add child packages if (entry.isDirectory()){ if (checkForInitPy(entry)){ childPackages.add(new PythonTreePackage( new Path(entry.getAbsolutePath()),this)); } continue; } if (entry.getName().endsWith(".py")) { PythonTreeFile f = new PythonTreeFile( new Path(entry.getAbsolutePath()), this); this.pythonFiles.add(f); this.childClasses.addAll(f.getClassesInFile()); if (entry.getName().equals("__init__.py")) { this.initPy = f; } this.getRoot().worked(); } } // get XMI:ID from __init__.py if (this.initPy != null) { this.xmi_id = ParseHelpers.extractXmiFromString( this.initPy.getFileContent()); if (this.xmi_id != null) this.getRoot().getXmiPackDict().put(this.xmi_id, this); } } /** * tests, if the given directory contains a file "__init__.py" * If yes, it can be considered as package. * * @param dir the directory to check * @return */ private boolean checkForInitPy(File dir) { if (! dir.isDirectory()) return false; for (File entry: dir.listFiles()) { if (entry.getName().equals("__init__.py")) return true; } return false; } /** * Writes a XMI-ID as a comment to this packages's __init__.py file * @param xmiID */ public void writeXmiID(String xmiID) { String initPyContent = this.getInitPy().getFileContent(); initPyContent = initPyContent.replaceAll("# PyUML: .*\n", ""); initPyContent = "# PyUML: Do not remove this line! # XMI_ID:" + xmiID + "\n" + initPyContent; File f = new File(this.getInitPy().getFilePath().toOSString()); ParseHelpers.stringToFile(f, initPyContent, this.getProject()); } /** * synchronize the model by this Package using * the packages/classes XMI-IDs */ public boolean synchronizeModel(NamedElement modelElement){ super.synchronizeModel(modelElement); Package modelPack = (Package) modelElement; Map xmiModelDict = this.getRoot().getXmiModelDict(); List processedElements = new Vector(); List childElements = new Vector(); childElements.addAll(this.getChildPackages()); childElements.addAll(this.getChildClasses()); for (PythonTreeNode pyChildNode : childElements) { String xmi_id= pyChildNode.getXmi_id(); boolean createNewElement = false; if (xmi_id != null) { if (xmiModelDict.containsKey(xmi_id)) { NamedElement modelChild = (NamedElement) xmiModelDict.get(xmi_id); // if found element is also child of model, // everything is OK, continue with packageSynchonization of children if (modelElement.getOwnedElements().contains(modelChild)) { pyChildNode.synchronizeModel(modelChild); processedElements.add(modelChild); } else { // element found, but at another place // -> move model element to be child of this model element if (modelChild instanceof Package) { Package childPack = (Package) modelChild; childPack.setNestingPackage(modelPack); } else if (modelChild instanceof Class || modelChild instanceof Interface) { Classifier childClass = (Classifier) modelChild; childClass.setPackage(modelPack); } // ## synchronize children ## // now the package has the right place, continue recursively if (! pyChildNode.synchronizeModel(modelChild)) return false; processedElements.add(modelChild); } } else { /* the xmi_id in the code is not present in model, but * in the code. (Propably it was deleted in the model) * This is not possible, because when this method is called * the code was supposed to change, not the model. * -> This is an inconsistency that cannot be resolved. * -> Because a Model element with a special xmi_id cannot * be created. * -> Show an Error message */ if (! this.getRoot().isCreateNewModel()) { boolean ignore = false; if (this.getRoot().getXmiModelDict().size() == 0) { ignore = MessageDialog.openQuestion(null, "Deleted Model", "The current python code was synchronized \n" + "with a UML model earlier.\n" + "Now, the UML model cannot be found.\n\n" + "Do you want to create a new UML model?"); } else { ignore = MessageDialog.openQuestion(null, "Model Synchronization error found", "The model is missing an element it formerly had\n" + "and which is still present in the code.\n" + "Obviously, the model was edited outside PyUml!\n\n" + "Please manually resolve the situation by removing\n" + "the XMI_ID comment in the __init__.py file of the\n" + "package or in the DocString comment of the class.\n\n" + "Element name: "+ pyChildNode.getName() +"\n\n"+ "Do you want to ignore this and create new model elements?"); } if (! ignore) return false; else { this.getRoot().setCreateNewModel(true); createNewElement=true; } } else { // the warning was disabled by user; createNewElement=true; } } } if (xmi_id==null || createNewElement) { // no xmi_id is present in the code -> this is a manually // created package! It must be created in the model, too. NamedElement modelNewChild = null; if (pyChildNode instanceof PythonTreePackage) { modelNewChild = modelPack.createNestedPackage(pyChildNode.getName()); } else if (pyChildNode instanceof PythonTreeClass) { modelNewChild = modelPack.createOwnedClass(pyChildNode.getName(), false); } // ## synchronize children ## if (modelNewChild != null) { // now the package is created, we can recursively edit it // by the pyPack synchronization method if (! pyChildNode.synchronizeModel(modelNewChild)) return false; processedElements.add(modelNewChild); } } } // test if all elements of model were processed for (Element modelChild : modelElement.getOwnedElements()) { if ((modelChild instanceof Class || modelChild instanceof Package) && (! processedElements.contains(modelChild))) // if this model element was not touched until now, // it obviously was deleted in code and must be deleted // also in model this.getRoot().getModelElementsToDestroy().add((NamedElement)modelChild); } return true; } public boolean synchronizeCode(NamedElement modelElement) throws PyUMLSynchronizeCodeException, PyUMLCancelledException { Map modelXmiDict = this.getRoot().getModelXmiDict(); Map xmiPackDict = this.getRoot().getXmiPackDict(); Map xmiClassDict = this.getRoot().getXmiClassDict(); Package modelPack = (Package) modelElement; // if package is to be renamed if (! modelPack.getName().equals(this.getName())) { // rename package; boolean success = this.renamePackage(modelPack.getName()); if (! success) throw new PyUMLSynchronizeCodeException(); // folder has changed -> return "true" to start from beginning return true; } // iterate over child elements of model globalLoop: for (Element modelChild:modelElement.getOwnedElements()) { //child package found if (modelChild instanceof Package) { Package modelChildPack = (Package) modelChild; // get xmi_id of model element String xmi_id = modelXmiDict.get(modelChildPack); if (xmiPackDict.containsKey(xmi_id)) { // this package is present in model // now we have to check it is also child of this package and // recurse over child elements. PythonTreePackage pyPack = xmiPackDict.get(xmi_id); if (this.getChildPackages().contains(pyPack)) { // child is Okay! boolean startNew = pyPack.synchronizeCode(modelChildPack); if (startNew) return true; } else { // move package to this package IPath newPath = EclipseHelperMethods.createFolder(this.getPackageDir().append(pyPack.name).toOSString(), this.getProject()).getFullPath(); boolean success = pyPack.movePackage(newPath); if (success) // now the package is moved, start from the beginning return true; else throw new PyUMLSynchronizeCodeException(); } } else { // xmi_id not found -> look for package with same name! boolean packageFound = false; for (PythonTreePackage pySubPack : this.getChildPackages()) { if (pySubPack.getName().equals(modelChildPack.getName())){ // write xmi_id to code (only needed once) pySubPack.writeXmiID(xmi_id); boolean startNew = pySubPack.synchronizeCode(modelChildPack); if (startNew) return true; packageFound=true; break; } } // if this package was not found, create it (recursively)! if (! packageFound) { // create package IPath newPackPath = getPackageDir().append(modelChildPack.getName()); File newPackDir = new File(newPackPath.toOSString()); newPackDir.mkdir(); EclipseHelperMethods.updateFolder(newPackDir.getAbsolutePath(), this.getProject()); File initPy = new File(newPackPath.append("__init__.py").toOSString()); try{ initPy.createNewFile(); EclipseHelperMethods.updateFile(initPy.getAbsolutePath(), this.getProject()); } catch (IOException e) { MessageDialog.openError(null, "IOException", "Error writing to file "+ initPy.getPath()); throw new PyUMLSynchronizeCodeException(); } PythonTreePackage newPyPack=null; try{ newPyPack = new PythonTreePackage(newPackPath, this); } catch (PyUMLParseException e){ e.printStackTrace(); } newPyPack.writeXmiID(xmi_id); this.childPackages.add(newPyPack); boolean startNew = newPyPack.synchronizeCode(modelChildPack); if (startNew) return true; } } } // handle child classes in model else if (modelChild instanceof Class || modelChild instanceof Interface) { Classifier modelChildClass = (Classifier) modelChild; /* only classes in packages are supported. If we find a top-level-class, * exit with warning! */ if (isRoot()) { MessageDialog.openWarning(null, "Error: Top level class detected!", "Please use in model and code *only* classes inside packages!\n" + "The class was: " + modelChildClass.getName()); continue globalLoop; } this.getRoot().worked(this.getName() + "/" + modelChildClass.getName()); this.getRoot().setSubTaskName(this.getName() + "/" + modelChildClass.getName()); String xmi_id = modelXmiDict.get(modelChildClass); // look for class with this xmi_id if (xmiClassDict.containsKey(xmi_id)) { PythonTreeClass pyClass = xmiClassDict.get(xmi_id); // if the class is already child of this package if (this.getChildClasses().contains(pyClass)) { // continue with children in model+code boolean startNew = pyClass.synchronizeCode(modelChildClass); while (startNew) { try { pyClass.getInFile().reInitFile(); startNew = this.synchronizeCode(modelElement); if (PythonTreePackage.completeNewStart) { PythonTreePackage.completeNewStart = false; return true; } } catch (PyUMLParseException e) { String message = "After changing the code parsing the " + "changed code failed!\n" + "Error was:\n" + e.getMessage(); e.printStackTrace(); throw new PyUMLSynchronizeCodeException(message); } } } else { // move class to this package; IPath newPath = this.getPackageDir().append(pyClass.name); boolean success = pyClass.moveClass(newPath); if (success) // now the class is moved, start from the beginning return true; else throw new PyUMLSynchronizeCodeException("Class could not be moved."); } } else { // no class with this xmi_id found! // look for python child class with same name // and write the right xmi_id boolean classFound = false; for (PythonTreeClass pySubClass : this.getChildClasses()) { if (pySubClass.getName().equals(modelChildClass.getName())){ classFound = true; // write xmi_id to code (only needed once) pySubClass.writeXmiID(xmi_id); // restart hole process setChanged(pySubClass.astNode.beginLine); try { pySubClass.getInFile().reInitFile(); this.synchronizeCode(modelElement); } catch (PyUMLParseException e) { String message = "After changing the code parsing the " + "changed code failed!\n" + "Error was:\n" + e.getMessage(); e.printStackTrace(); throw new PyUMLSynchronizeCodeException(message); } break; } } if (! classFound) { // if this class was not found, create it (recursively)! boolean success = PythonTreeClass.createNewClass(modelChildClass, this); if (success) { File newClassFile = new File(this.getPackageDir().append(modelChildClass.getName()).toOSString() + ".py"); try { PythonTreeFile newPyFile = new PythonTreeFile(Path.fromOSString(newClassFile.toString()), this); this.childClasses.addAll(newPyFile.getClassesInFile()); this.synchronizeCode(modelElement); break globalLoop; } catch (PyUMLParseException e) { throw new PyUMLSynchronizeCodeException( "Error: The newly created class" + newClassFile + "\ncannot be parsed!\n" + "Synchronizing Code cannot continue!\n" + "Message was:\n" + e.getClass().getName()+"\n"+e.getMessage()); } //OK, class was created! } else { throw new PyUMLSynchronizeCodeException("Error creating class: "+modelChildClass.getName()); } } } } } return false; } /** * Move this package in the python code to another package/folder * * Rename the package directory * * TODO: Update references * * @param newPath the IPath where the package is to be moved * @return true on success, false if package already exists */ public boolean movePackage(IPath newPath) { IFolder packDir = EclipseHelperMethods.createFolder(this.getPackageDir().toOSString(), this.getProject()); if (! packDir.exists()) { MessageDialog.openError(null, "Error moving package", "Package " + this.name + " cannot " + "be renamed\n because it does not seem to exist!"); return false; } try { packDir.move(newPath, true, null); } catch (CoreException e) { MessageDialog.openError(null, "Error moving package", "The package " +this.name+ " cannot" + " be moved to "+ newPath +".\n Propably a package with the new name already exists!" + "\n\nReason:\n" +e.getMessage()); return false; } return true; } /** * Rename this package in the python code * * Rename the package directory * * TODO: Update references * * @param newName the new name of the package in the same directory * @return true on success, false if package already exists */ public boolean renamePackage(String newName) { IFolder packDir = EclipseHelperMethods.createFolder(this.getPackageDir().toOSString(), this.getProject()); IPath newPath = packDir.getFullPath().removeLastSegments(1).append(newName); return this.movePackage(newPath); } /** * Submits 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 line the changed line in the file */ private void setChanged(int line) throws PyUMLSynchronizeCodeException{ this.getRoot().setChangedFileLine(this.packageDir+":"+line); } /** * Puts a new Package as a child beneath this package. * @param pack */ public void addChildPackage(PythonTreePackage pack) { childPackages.add(pack); } /** * returns all child Packages */ public List getChildPackages() { return this.childPackages; } /** * returns all child classes (as PythonTreeClass) * @return */ public List getChildClasses() { return childClasses; } /** * @return __init__.py file of this package */ public PythonTreeFile getInitPy() { return initPy; } public IPath getPackageDir() { return packageDir; } /** * gets the parent package of this package, * null if the parent package is root */ public PythonTreePackage getParent() { return (PythonTreePackage) super.getParent(); } }