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

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