Eclipse-PyUML/pyUml/src/pyUML/pythonTree/PythonTreeRoot.java

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;
}
}