530 lines
16 KiB
Java
Executable File
530 lines
16 KiB
Java
Executable File
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<IPath> srcPaths;
|
|
private Map<String, PythonTreePackage> xmiPackDict;
|
|
private Map<String, PythonTreeClass> xmiClassDict;
|
|
private Map<String, PythonTreeClass> classDict;
|
|
private Map<String, String> classNameDict;
|
|
private List<String> renamedClasses;
|
|
|
|
// Attributes needed for model synchronization
|
|
private Map<String, EObject> xmiModelDict;
|
|
private Map<EObject, String> modelXmiDict;
|
|
private Map<String, EObject> xmiModelDictOld;
|
|
private List<NamedElement> modelElementsToDestroy;
|
|
|
|
private List<String> changedFileLines;
|
|
private boolean createNewModel = false;
|
|
private boolean showWarnings = true;
|
|
private IProgressMonitor monitor;
|
|
private List<String> 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<String, PythonTreePackage>();
|
|
this.xmiClassDict = new Hashtable<String, PythonTreeClass>();
|
|
this.classDict = new Hashtable<String, PythonTreeClass>();
|
|
this.classNameDict = new Hashtable<String, String>();
|
|
|
|
|
|
// 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<File> checkPackDirs = new Vector<File>();
|
|
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<File> childDirList =new Vector<File>();
|
|
|
|
// 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<String, EObject> 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<NamedElement>();
|
|
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<Relationship> rels = new Vector<Relationship>();
|
|
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<Generalization> modelGenerals = modelClass.getGeneralizations();
|
|
Map<Classifier, Generalization> modelSuperClasses =
|
|
new Hashtable<Classifier, Generalization>();
|
|
for (Generalization gen : modelGenerals) {
|
|
if (gen.getGeneral() != null)
|
|
modelSuperClasses.put(gen.getGeneral(), gen);
|
|
}
|
|
|
|
// get list of superclasses in python code
|
|
Set<PythonTreeClass> 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<Generalization> modelGeneralList = new Vector<Generalization>();
|
|
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<Relationship> rels = new Vector<Relationship>();
|
|
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<EObject,
|
|
String> modelXmiDict, Map<String, EObject> xmiModelDictOld,
|
|
IProgressMonitor monitor, int classCount)
|
|
throws PyUMLCancelledException{
|
|
this.monitor = monitor;
|
|
this.modelXmiDict = modelXmiDict;
|
|
this.xmiModelDictOld = xmiModelDictOld;
|
|
this.renamedClasses = new Vector<String>();
|
|
this.showWarnings = false;
|
|
this.changedFileLines = new Vector<String>();
|
|
this.progressFinishedItems = new Vector<String>();
|
|
|
|
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<String, PythonTreeClass> getClassDict() {
|
|
return classDict;
|
|
}
|
|
|
|
/**
|
|
* returns the XMI-ID <-> PythonTreePackage dictionary with XMI-IDs as keys
|
|
* @return
|
|
*/
|
|
|
|
public Map<String, PythonTreePackage> getXmiPackDict() {
|
|
return xmiPackDict;
|
|
}
|
|
|
|
/**
|
|
* returns the XMI-ID <-> PythonTreeClass dictionary with XMI-IDs as keys
|
|
* @return
|
|
*/
|
|
public Map<String, PythonTreeClass> getXmiClassDict() {
|
|
return xmiClassDict;
|
|
}
|
|
|
|
/**
|
|
* returns the XMI-ID <-> model dictionary with XMI-IDs as keys
|
|
* @return
|
|
*/
|
|
public Map<String, EObject> getXmiModelDict() {
|
|
return xmiModelDict;
|
|
}
|
|
|
|
/**
|
|
* returns the XMI-ID <-> model dictionary with model elements as keys
|
|
* @return
|
|
*/
|
|
public Map<EObject, String> 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<NamedElement> 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<String, EObject> getXmiModelDictOld() {
|
|
return xmiModelDictOld;
|
|
}
|
|
|
|
public IProject getProject() {
|
|
return project;
|
|
}
|
|
|
|
public List<String> getRenamedClasses() {
|
|
return renamedClasses;
|
|
}
|
|
|
|
public boolean isShowWarnings() {
|
|
return showWarnings;
|
|
}
|
|
|
|
public Map<String, String> getClassNameDict() {
|
|
return classNameDict;
|
|
}
|
|
}
|