package pyUML.pythonTree; import java.io.File; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.widgets.Display; import org.eclipse.uml2.uml.Classifier; import org.eclipse.uml2.uml.Generalization; import org.eclipse.uml2.uml.Model; import org.eclipse.uml2.uml.NamedElement; import org.eclipse.uml2.uml.Package; import org.eclipse.uml2.uml.Relationship; import pyUML.backend.EclipseHelperMethods; import pyUML.backend.JavaHelperMethods; import pyUML.exceptions.PyUMLCancelledException; import pyUML.exceptions.PyUMLParseException; import pyUML.exceptions.PyUMLSynchronizeCodeException; /** * This class is the root of the Python Syntax Tree * it represents the parent directory of the uppermost * package in the python package structure. * * If the Project directory is already a package * (contains __init__.py), then this class will * contain a Package representing the own directory * as child. * * Typically, this represents the "src" directory * of the eclipse project. * * The root node corresponds with the "model" node of the UML diagram. * */ public class PythonTreeRoot extends PythonTreePackage{ private IProject project; private Set srcPaths; private Map xmiPackDict; private Map xmiClassDict; private Map classDict; private Map classNameDict; private List renamedClasses; // Attributes needed for model synchronization private Map xmiModelDict; private Map modelXmiDict; private Map xmiModelDictOld; private List modelElementsToDestroy; private List changedFileLines; private boolean createNewModel = false; private boolean showWarnings = true; private IProgressMonitor monitor; private List progressFinishedItems; /** * createNewModel is true, if a user decided to create a new model, * even though a model former existed and was obviously deleted * or edited outside PyUML * initially, createNewModel is false, and if a former model is detected, * the user is asked * @return the current value of is CreateNewModel */ public boolean isCreateNewModel() { return createNewModel; } /** * if a user decided to create a new model, * even though a model former existed and was obviously deleted * or edited outside PyUML, this decision can be saved here so that * the user is asked only once * @param createNewModel -> true, if the user decided to re-create the model */ public void setCreateNewModel(boolean createNewModel) { this.createNewModel = createNewModel; } /** * sets the last changed line on synchronizing code. * * Background: * on synchronizing code, after every change the whole code is re-read * an synchronizing started from beginning, until there are no more changes. * The danger is that the program runs in an endless loop, e.g. if changes * are not sufficient (this should NOT happen normally) * To detect this situation, the line of the last change in code is saved; * if this line does not change for many times, we detect a dead loop * and break it with an error message * * @param changedFileLine a line, typically representing the changed line * in code. It can also hold a package/file path etc. * It must always be the same for the same operation! */ public void setChangedFileLine(String changedFileLine) throws PyUMLSynchronizeCodeException{ if (this.changedFileLines.size() == 0){ this.changedFileLines.add(changedFileLine); return; } if (this.changedFileLines.get(this.changedFileLines.size()-1).equals(changedFileLine)) { this.changedFileLines.add(changedFileLine); } else { this.changedFileLines.clear(); this.changedFileLines.add(changedFileLine); } if (this.changedFileLines.size() > 20) { String message = "Error: Synchronize Code runs in an unfinished loop!\n\n" + "The loop is in the File/Line\n" + changedFileLine; throw new PyUMLSynchronizeCodeException(message); } } /** * * @param project the eclipse IProject * @param showWarnings if true, warnings (e.g. duplicate classes) are shown * this is typically done only on the first run, if there are several runs */ public PythonTreeRoot(IProject project, boolean showWarnings, IProgressMonitor monitor) throws PyUMLParseException, PyUMLCancelledException{ super(null, null); this.project = project; this.showWarnings = showWarnings; this.monitor = monitor; this.srcPaths = EclipseHelperMethods.getPythonSrcDirs(project); // first Source dir is standard dir -> all new packages will be // created here! this.packageDir = this.srcPaths.iterator().next(); int numPackages=JavaHelperMethods.getFileCount(this.packageDir.toFile(), "^.*.py$"); this.beginTask("Analyze Python Code", numPackages); initRoot(); } /** * On object Creation, this initializes the root of the python * code representationS * @throws PyUMLParseException */ private void initRoot() throws PyUMLParseException, PyUMLCancelledException{ // initialize name and xmi_id dictionaries this.xmiPackDict = new Hashtable(); this.xmiClassDict = new Hashtable(); this.classDict = new Hashtable(); this.classNameDict = new Hashtable(); // set the Tree name (= project name = model name) this.name = this.project.getName(); // iterate over all source paths: find uppermost package(s) // and append it to this root node. for (IPath path : this.srcPaths) { File srcDir = new File(path.toOSString()); if (! srcDir.isDirectory()) continue; // check if dir is already package. // if yes, add it // if no, check all child dirs (until packages were found) List checkPackDirs = new Vector(); checkPackDirs.add(srcDir); while(checkPackDirs.size() > 0) { //take the next subdir File dir = checkPackDirs.get(0); checkPackDirs.remove(dir); // list dir File[] dirList = dir.listFiles(); boolean isPackage=false; List childDirList =new Vector(); // look for child directories or "__init__.py" for (File entry:dirList) { if (entry.isDirectory()) childDirList.add(entry); else if (entry.getName().equals("__init__.py")) isPackage=true; } // if this dir is a package, initialize a new PythonTreePackage if (isPackage) this.addChildPackage( new PythonTreePackage( new Path(dir.getAbsolutePath()), this)); else checkPackDirs.addAll(childDirList); } } } /** * For a given model (it can be empty), synchronize the Model * with the code represented by this PythonTreeNode * use xmi_id for identifying moved Packages * * @param model * @param xmiModelDict * @return true on success, false on Errors (Model must not be saved) */ public boolean synchronizeModel(Model model, Map xmiModelDict) throws PyUMLCancelledException{ // set name of model (e.g. when newly created if (! model.getName().equals(this.name)) model.setName(this.name); this.xmiModelDict = xmiModelDict; this.modelElementsToDestroy = new Vector(); boolean success = super.synchronizeModel(model); if (! success) return false; // create Generalizations this.createModelGeneralizations(model); // delete elements not present in model any more for (NamedElement elementToDestroy : this.modelElementsToDestroy) { // destroy all relationships Vector rels = new Vector(); for (Relationship rel : elementToDestroy.getRelationships()) rels.add(rel); for (Relationship rel : rels) rel.destroy(); elementToDestroy.destroy(); } return true; } /** * after synchronizeModel was run, this method creates all generals * in the model. That can be done only *after* the model synchronized, * to ensure all classes are present in the model. * @param model */ public void createModelGeneralizations(Model model) { for (PythonTreeClass pyClass : classDict.values()) { // get model class of this class Classifier modelClass = (Classifier) pyClass.getAssociatedModelElement(); // get all model superclasses for this class List modelGenerals = modelClass.getGeneralizations(); Map modelSuperClasses = new Hashtable(); for (Generalization gen : modelGenerals) { if (gen.getGeneral() != null) modelSuperClasses.put(gen.getGeneral(), gen); } // get list of superclasses in python code Set pySuperClasses = pyClass.getGeneralizationsInProject(); // ensure all python superclasses are in model for (PythonTreeClass pySuperClass : pySuperClasses) { if (! modelSuperClasses.containsKey((Classifier)pySuperClass.getAssociatedModelElement())) { modelClass.createGeneralization( (Classifier)pySuperClass.getAssociatedModelElement()); } } // ensure all model superclasses are present in python. // if not, delete model superclass List modelGeneralList = new Vector(); modelGeneralList.addAll(modelGenerals); for (Generalization modelGen : modelGeneralList) { Classifier modelSuperClass = modelGen.getGeneral(); if (modelSuperClass == null) { modelGen.destroy(); continue; } // if no class with the Generalization exists if (! this.getClassDict().containsKey(this.getModelPackageStructure(modelSuperClass) + modelSuperClass.getName())) { // destroy generalization modelGen.destroy(); } else { PythonTreeClass pySuperClass = this.getClassDict().get(this.getModelPackageStructure(modelSuperClass) + modelSuperClass.getName()); // if Superclass exists, but is no superclass if (! pySuperClasses.contains(pySuperClass)) { // destroy all relationships Vector rels = new Vector(); for (Relationship rel : modelSuperClasses.get(modelSuperClass).getRelationships()) rels.add(rel); for (Relationship rel : rels) rel.destroy(); // destroy superclass modelSuperClasses.get(modelSuperClass).destroy(); } } //otherwise: everything is OK } } } /** * * @param modelElement * @param modelXmiDict * @param xmiModelDictOld * @param monitor * @param classCount the number of classes in model - optional parameter for progress bar * @return */ public boolean synchronizeCode(NamedElement modelElement, Map modelXmiDict, Map xmiModelDictOld, IProgressMonitor monitor, int classCount) throws PyUMLCancelledException{ this.monitor = monitor; this.modelXmiDict = modelXmiDict; this.xmiModelDictOld = xmiModelDictOld; this.renamedClasses = new Vector(); this.showWarnings = false; this.changedFileLines = new Vector(); this.progressFinishedItems = new Vector(); this.beginTask("Synchronize Code by Model", classCount); // synchronize until no more changes were found (synchronize returns false) try { while(super.synchronizeCode(modelElement)) { // if synchronizeCode returned true, it just changed the code! // -> Re-read the code and start from the beginning this.initRoot(); this.beginTask("Synchronize Code by Model", classCount*2); } // finally, do a second sync run to ensure all newly // created classes can be used in import statements super.synchronizeCode(modelElement); } catch (PyUMLSynchronizeCodeException e) { e.printStackTrace(); MessageDialog.openError(null, "An Exception happened while synchronizing code", "An Exception happened while synchronizing code.\n" + "Message was: \n\n" + e.getMessage()); return false; } catch (PyUMLParseException e) { e.printStackTrace(); MessageDialog.openError(null, "An Exception happened while synchronizing code", "Obviously, while synchronizing code a python syntax error was\n" + "created. This should not happen!\n" + "Please resolve the problem manually!\n\n" + "Message was: \n" + e.getMessage()); return false; } return true; } public boolean isRoot() { return true; } /** * returns a dictionary of all class names with their corresponding * PythonTreeClass * @return */ public Map getClassDict() { return classDict; } /** * returns the XMI-ID <-> PythonTreePackage dictionary with XMI-IDs as keys * @return */ public Map getXmiPackDict() { return xmiPackDict; } /** * returns the XMI-ID <-> PythonTreeClass dictionary with XMI-IDs as keys * @return */ public Map getXmiClassDict() { return xmiClassDict; } /** * returns the XMI-ID <-> model dictionary with XMI-IDs as keys * @return */ public Map getXmiModelDict() { return xmiModelDict; } /** * returns the XMI-ID <-> model dictionary with model elements as keys * @return */ public Map getModelXmiDict() { return modelXmiDict; } /** * Like PythonTreeClass.getPackageStructure(), this * generates a string with the Package structure containing * a class, like "/supPack/subPack/" * @param c The model class to use * @return the parent package structure as a String */ public String getModelPackageStructure(Classifier c) { Package parent = c.getPackage(); String structure = ""; do { structure = "/"+parent.getName() + structure; parent = parent.getNestingPackage(); } while (! (parent instanceof Model)); return structure + "/"; } /** * Proxy method. Runs beginTask, if there is a monitor, * does nothing, otherwise * @param name The name of the task to start * @param totalWork The number of task steps to display */ public void beginTask(String name, int totalWork) { if (this.monitor != null) this.monitor.beginTask(name, totalWork); } /** * Completes a sub-step in the progress monitor, * if any. */ public void worked() throws PyUMLCancelledException { if (this.monitor.isCanceled()) throw new PyUMLCancelledException(); if (this.monitor != null) { monitor.worked(1); } // update UI while (Display.getCurrent().readAndDispatch()) ; } /** * Sets the name of the current task in the progress monitor * @param name * @throws PyUMLCancelledException */ public void setSubTaskName(String name) throws PyUMLCancelledException{ if (this.monitor.isCanceled()) throw new PyUMLCancelledException(); if (this.monitor != null) { monitor.subTask(name); } // update UI while (Display.getCurrent().readAndDispatch()) ; } /** * like worked(), this additionally has the possibility to * put an item name of the finished item, so that * the progress bar will only be updated the first time an item is * finished. * This is useful for a progress bar with recursive sync calls * @param itemName the name of the finished item */ public void worked(String itemName) throws PyUMLCancelledException{ if (! this.progressFinishedItems.contains(itemName)) { this.progressFinishedItems.add(itemName); this.worked(); } } public void setMonitor(IProgressMonitor monitor) { this.monitor = monitor; } /** * returns the list of model elements that are to be removed from * the model after synchronizing. * This has to be done as the last step of the sync process * so that no concurrent modification is done on the model. * @return */ public List getModelElementsToDestroy() { return modelElementsToDestroy; } /** * Overwrites the method fromPythonTreePackage * -> On a renaming of the model root nothing is to be done! * @param newName */ public boolean renamePackage(String newName) { return true; } public Map getXmiModelDictOld() { return xmiModelDictOld; } public IProject getProject() { return project; } public List getRenamedClasses() { return renamedClasses; } public boolean isShowWarnings() { return showWarnings; } public Map getClassNameDict() { return classNameDict; } }