542 lines
18 KiB
Java
Executable File
542 lines
18 KiB
Java
Executable File
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<PythonTreePackage> childPackages;
|
|
private List<PythonTreeClass> childClasses;
|
|
private List<PythonTreeFile> 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<PythonTreeClass>();
|
|
this.childPackages = new Vector<PythonTreePackage>();
|
|
this.pythonFiles = new Vector<PythonTreeFile>();
|
|
|
|
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<String, EObject> xmiModelDict = this.getRoot().getXmiModelDict();
|
|
|
|
List<NamedElement> processedElements = new Vector<NamedElement>();
|
|
List<PythonTreeNode> childElements = new Vector<PythonTreeNode>();
|
|
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<EObject, String> modelXmiDict = this.getRoot().getModelXmiDict();
|
|
Map<String, PythonTreePackage> xmiPackDict = this.getRoot().getXmiPackDict();
|
|
Map<String, PythonTreeClass> 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<PythonTreePackage> getChildPackages() {
|
|
return this.childPackages;
|
|
}
|
|
|
|
/**
|
|
* returns all child classes (as PythonTreeClass)
|
|
* @return
|
|
*/
|
|
public List<PythonTreeClass> 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();
|
|
}
|
|
}
|