2008-02-20 14:27:19 +01:00
|
|
|
package pyUML.pythonTree;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.Hashtable;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Set;
|
|
|
|
import java.util.Vector;
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
import org.eclipse.core.resources.IFile;
|
|
|
|
import org.eclipse.core.runtime.CoreException;
|
|
|
|
import org.eclipse.core.runtime.IPath;
|
|
|
|
import org.eclipse.emf.common.util.BasicEList;
|
|
|
|
import org.eclipse.emf.common.util.EList;
|
|
|
|
import org.eclipse.emf.ecore.EObject;
|
2009-01-12 11:41:06 +01:00
|
|
|
import org.eclipse.gmf.runtime.common.core.util.StringUtil;
|
2008-02-20 14:27:19 +01:00
|
|
|
import org.eclipse.jface.dialogs.MessageDialog;
|
|
|
|
import org.eclipse.uml2.uml.Association;
|
|
|
|
import org.eclipse.uml2.uml.Class;
|
|
|
|
import org.eclipse.uml2.uml.Classifier;
|
|
|
|
import org.eclipse.uml2.uml.Element;
|
|
|
|
import org.eclipse.uml2.uml.Generalization;
|
|
|
|
import org.eclipse.uml2.uml.Interface;
|
|
|
|
import org.eclipse.uml2.uml.NamedElement;
|
|
|
|
import org.eclipse.uml2.uml.Operation;
|
|
|
|
import org.eclipse.uml2.uml.Property;
|
|
|
|
import org.eclipse.uml2.uml.Realization;
|
|
|
|
import org.eclipse.uml2.uml.Relationship;
|
|
|
|
import org.eclipse.uml2.uml.Type;
|
|
|
|
import org.eclipse.uml2.uml.VisibilityKind;
|
|
|
|
import org.python.pydev.parser.jython.ast.Assign;
|
|
|
|
import org.python.pydev.parser.jython.ast.Attribute;
|
2009-01-12 11:41:06 +01:00
|
|
|
import org.python.pydev.parser.jython.ast.Break;
|
2008-02-20 14:27:19 +01:00
|
|
|
import org.python.pydev.parser.jython.ast.ClassDef;
|
|
|
|
import org.python.pydev.parser.jython.ast.Expr;
|
|
|
|
import org.python.pydev.parser.jython.ast.FunctionDef;
|
|
|
|
import org.python.pydev.parser.jython.ast.Name;
|
|
|
|
import org.python.pydev.parser.jython.ast.NameTok;
|
|
|
|
import org.python.pydev.parser.jython.ast.Str;
|
|
|
|
import org.python.pydev.parser.jython.ast.exprType;
|
|
|
|
import org.python.pydev.parser.jython.ast.stmtType;
|
|
|
|
|
|
|
|
import pyUML.backend.EclipseHelperMethods;
|
|
|
|
import pyUML.backend.GlobalConstants;
|
2008-04-10 23:54:34 +02:00
|
|
|
import pyUML.backend.JavaHelperMethods;
|
2008-02-20 14:27:19 +01:00
|
|
|
import pyUML.backend.ParseHelpers;
|
|
|
|
import pyUML.exceptions.PyUMLCancelledException;
|
|
|
|
import pyUML.exceptions.PyUMLParseException;
|
|
|
|
import pyUML.exceptions.PyUMLSynchronizeCodeException;
|
|
|
|
import pyUML.refactoring.BicycleRefactoring;
|
|
|
|
import pyUML.refactoring.FileRefactoring;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Part of PythonTree representing a class
|
|
|
|
* - can write XMI ID to classes docstring
|
|
|
|
*
|
|
|
|
*/
|
2009-01-12 11:41:06 +01:00
|
|
|
public class PythonTreeClass extends PythonTreeNode {
|
|
|
|
|
|
|
|
private static final Pattern FROM_IMPORT_PATTERN = Pattern.compile("[\\s]*from[\\s]*([^\\s]*)[\\s]*import[\\s]*([^\\s#]*)([\\s#]?.*)");
|
|
|
|
|
2008-02-20 14:27:19 +01:00
|
|
|
ClassDef astNode;
|
|
|
|
String docString;
|
|
|
|
PythonTreeFile inFile;
|
|
|
|
int docStringLine;
|
|
|
|
int docStringCol;
|
|
|
|
int lastLine;
|
|
|
|
List<String> usedStereotypes;
|
|
|
|
Set<String> superClasses;
|
|
|
|
private String indent = " ";
|
|
|
|
Map<String, PythonTreeMethod> childMethodDict;
|
|
|
|
Map<String, PythonTreeAttribute> childStaticAttrDict;
|
|
|
|
Map<String, PythonTreeAttribute> childObjectAttrDict;
|
2009-01-12 11:41:06 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A map holding the full package name in the format mypackage/mysubpackage/MyClass
|
|
|
|
* indexed by the classname which might be the plain classname if it has been
|
|
|
|
* imported using a "from mypackage.mysubpackage import MyClass" statement
|
|
|
|
* or prefixed with the package / alias if imported with a simple
|
|
|
|
* "import mypackage.mysubpackage" or "import mypackage.mysubpackage as myalias".
|
|
|
|
*/
|
|
|
|
private Map<String, String> packageCache;
|
|
|
|
|
2008-02-20 14:27:19 +01:00
|
|
|
/**
|
|
|
|
* Constructor to create a PythonTreeClass of a python class AST
|
|
|
|
* @param parent the PythonTreePackage, which is parent of this class
|
|
|
|
* @param inFile the PythonTreeFile
|
|
|
|
* @param astNode the corresponding Python AST node
|
|
|
|
* @param lastLine the last line of this class definition; 0 if
|
|
|
|
* class ends with end of file
|
|
|
|
*/
|
|
|
|
public PythonTreeClass(PythonTreeNode parent, PythonTreeFile inFile, ClassDef astNode, int lastLine) throws PyUMLParseException, PyUMLCancelledException{
|
|
|
|
super(parent);
|
|
|
|
this.astNode = astNode;
|
|
|
|
this.inFile = inFile;
|
|
|
|
this.lastLine = lastLine;
|
|
|
|
this.superClasses = new HashSet<String>();
|
|
|
|
this.childMethodDict = new Hashtable<String, PythonTreeMethod>();
|
|
|
|
this.childStaticAttrDict = new Hashtable<String, PythonTreeAttribute>();
|
|
|
|
this.childObjectAttrDict = new Hashtable<String, PythonTreeAttribute>();
|
2009-01-12 11:41:06 +01:00
|
|
|
packageCache = new HashMap<String, String>();
|
2008-02-20 14:27:19 +01:00
|
|
|
this.initClassNode();
|
|
|
|
this.initSubStructures();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private void initClassNode() throws PyUMLParseException, PyUMLCancelledException{
|
2009-01-12 11:41:06 +01:00
|
|
|
this.name = ((NameTok)this.astNode.name).id;
|
2008-02-20 14:27:19 +01:00
|
|
|
this.docStringLine = this.astNode.beginLine;
|
|
|
|
this.docStringCol = this.astNode.beginColumn;
|
|
|
|
|
|
|
|
|
|
|
|
// get class Body
|
|
|
|
stmtType[] classBody = this.astNode.body;
|
|
|
|
if (classBody.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// extract DocString, if it exists
|
|
|
|
if (classBody[0] instanceof Expr) {
|
|
|
|
Expr exp = (Expr) classBody[0];
|
|
|
|
if (exp.value instanceof Str) {
|
|
|
|
Str stringExp = (Str) exp.value;
|
|
|
|
if (stringExp.type == Str.TripleDouble) {
|
|
|
|
this.docString=stringExp.s;
|
|
|
|
this.docStringLine = stringExp.beginLine;
|
|
|
|
this.docStringCol = stringExp.beginColumn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//extract XMI-ID from DocString
|
|
|
|
if (this.docString != null) {
|
|
|
|
this.xmi_id = ParseHelpers.extractXmiFromString(this.docString);
|
|
|
|
if (this.xmi_id != null)
|
|
|
|
this.getRoot().getXmiClassDict().put(this.xmi_id, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
PythonTreeRoot root = this.getRoot();
|
|
|
|
|
|
|
|
// test if a class of this name already exists
|
2009-01-12 11:41:06 +01:00
|
|
|
String structuredName = this.getPackageStructure() + this.name;
|
2008-02-20 14:27:19 +01:00
|
|
|
this.getRoot().setSubTaskName(structuredName);
|
|
|
|
|
|
|
|
if (root.getClassDict().containsKey(structuredName) && root.isShowWarnings()) {
|
|
|
|
String message = "Duplicate class detected!\n" +
|
|
|
|
"More than one class with the name " + this.name
|
|
|
|
+ " was found \n"
|
|
|
|
+ "in the package "+this.getPackageStructure() +"."
|
|
|
|
+ "\nThis can lead to problems in PyUML.\n"
|
|
|
|
+ "Please rename one of the classes in the code!\n"
|
|
|
|
+ "Both classes will be ignored!";
|
|
|
|
this.getParent().getChildClasses().remove(this.getRoot().getClassDict().get(structuredName));
|
|
|
|
this.getRoot().getClassDict().remove(structuredName);
|
|
|
|
this.getRoot().getClassNameDict().remove(structuredName);
|
|
|
|
|
|
|
|
MessageDialog.openWarning(null, "Duplicate class detected", message);
|
|
|
|
return;
|
|
|
|
//throw new PyUMLParseException(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
// initialize empty set of superclasses
|
|
|
|
root.getClassDict().put(structuredName, this);
|
|
|
|
root.getClassNameDict().put(structuredName, this.name);
|
|
|
|
|
2009-01-12 11:41:06 +01:00
|
|
|
for (int i = 0; i < this.astNode.bases.length; i++) {
|
2008-04-10 23:54:34 +02:00
|
|
|
exprType superClass = this.astNode.bases[i];
|
2009-01-12 11:41:06 +01:00
|
|
|
// check whether it's a plain string, which means a classname
|
|
|
|
// or separated by dots
|
2008-02-20 14:27:19 +01:00
|
|
|
if (superClass instanceof Name) {
|
|
|
|
String superName = ((Name)superClass).id;
|
|
|
|
String superClassPackString = this.getPackFromImports(superName);
|
|
|
|
if (superClassPackString != null)
|
2009-01-12 11:41:06 +01:00
|
|
|
this.superClasses.add(superClassPackString);
|
|
|
|
} else if (superClass instanceof Attribute) {
|
2008-04-10 23:54:34 +02:00
|
|
|
// Just extract the content in the paranthesis without
|
|
|
|
// any further analysis
|
|
|
|
// This can be a subclass or a class under a package
|
|
|
|
/// -> this cannot be recognized
|
|
|
|
|
2008-02-20 14:27:19 +01:00
|
|
|
Attribute superAtt = (Attribute) superClass;
|
2008-04-10 23:54:34 +02:00
|
|
|
String line = FileRefactoring.getLine(inFile, superAtt.beginLine);
|
|
|
|
|
|
|
|
// find i'th component of bases -> current component
|
|
|
|
String separator = "(";
|
|
|
|
for (int j=0; j <= i; j++) {
|
|
|
|
if (j > 0)
|
|
|
|
separator = ",";
|
|
|
|
line = line.substring(line.indexOf(separator) + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( i < this.astNode.bases.length - 1)
|
|
|
|
separator = ",";
|
|
|
|
else
|
|
|
|
separator = ")";
|
|
|
|
|
|
|
|
String superClassFullName = line.substring(0, line.indexOf(separator));
|
|
|
|
superClassFullName = superClassFullName.replaceAll("[\\s]", "");
|
2009-01-12 11:41:06 +01:00
|
|
|
// currently we suppose that the class will be prefixed by
|
|
|
|
// either the fully qualified package name or an alias,
|
|
|
|
// nested classes are not supported yet!
|
|
|
|
String superClassPackString = this.getPackFromImports(superClassFullName);
|
|
|
|
if (superClassPackString != null) { // TODO: will a null value ever return?
|
|
|
|
this.superClasses.add(superClassPackString);
|
|
|
|
}
|
2008-02-20 14:27:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* initializes all Substructures of this class
|
|
|
|
* - Methods (object or static)
|
|
|
|
* - Static attributes (in class body)
|
|
|
|
* - Object attributes (in __init(self))
|
|
|
|
*
|
|
|
|
* The PythonTree child objects are created and initialized
|
|
|
|
* in their constructor
|
|
|
|
*/
|
|
|
|
private void initSubStructures() throws PyUMLParseException{
|
|
|
|
// parse class body
|
|
|
|
stmtType[] statements = this.astNode.body;
|
|
|
|
for (int i=0; i<statements.length; i++) {
|
|
|
|
stmtType statement = statements[i];
|
|
|
|
|
|
|
|
|
|
|
|
// fix indent, if needed
|
|
|
|
String line = this.inFile.getSplittedFileContent()[statement.beginLine - 1];
|
|
|
|
int indentLength = 0;
|
|
|
|
|
|
|
|
if (line.length() > 0)
|
|
|
|
for (; line.charAt(indentLength) == ' ' && line.length() > indentLength +1; indentLength++) ;
|
|
|
|
|
|
|
|
if (this.indent.length() != indentLength && ! line.matches("^[\\s]*\\\"\\\"\\\".*$")) {
|
|
|
|
this.indent = "";
|
|
|
|
for (int j=0; j< indentLength; j++)
|
|
|
|
this.indent += " ";
|
|
|
|
}
|
|
|
|
|
|
|
|
// get functions and object attributes
|
|
|
|
|
|
|
|
int nextLine;
|
|
|
|
if (i < statements.length -1)
|
|
|
|
nextLine = statements[i+1].beginLine;
|
|
|
|
else
|
|
|
|
nextLine = (this.lastLine == -1) ? -1 : this.lastLine + 1;
|
|
|
|
|
|
|
|
if (statement instanceof FunctionDef) {
|
|
|
|
// get line of next statement -> for a static method, this should
|
|
|
|
// be the staticmethod call
|
|
|
|
|
|
|
|
PythonTreeMethod childMethod = new PythonTreeMethod(this, (FunctionDef) statement, nextLine);
|
|
|
|
|
|
|
|
this.childMethodDict.put(childMethod.getName(), childMethod);
|
|
|
|
// look for object attributes in __init__
|
|
|
|
if (childMethod.getName().equals("__init__")) {
|
|
|
|
stmtType[] methodStmts = childMethod.getAstNode().body;
|
|
|
|
// iterate over all method body statements
|
|
|
|
for (int j=0; j < methodStmts.length; j++) {
|
|
|
|
int nextStmtLine = nextLine;
|
|
|
|
if (j < methodStmts.length - 1 )
|
|
|
|
nextStmtLine = methodStmts[j+1].beginLine;
|
|
|
|
stmtType methodStmt = methodStmts[j];
|
|
|
|
if (methodStmt instanceof Assign) {
|
|
|
|
// need line of next statement to be able
|
|
|
|
// to multi-line-expressions
|
|
|
|
if (j < methodStmts.length-1)
|
|
|
|
nextStmtLine = methodStmts[j+1].beginLine;
|
|
|
|
PythonTreeAttribute childAtt = new PythonTreeAttribute(this, (Assign) methodStmt, false, nextStmtLine);
|
|
|
|
if (childAtt.isValid())
|
|
|
|
this.childObjectAttrDict.put(childAtt.getName(), childAtt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get static attributes
|
|
|
|
else if (statement instanceof Assign) {
|
|
|
|
PythonTreeAttribute childAtt = new PythonTreeAttribute(this, (Assign) statement, true, nextLine);
|
|
|
|
if (childAtt.isValid())
|
|
|
|
this.childStaticAttrDict.put(childAtt.getName(), childAtt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write/update the given XMI id to the classes docstring
|
|
|
|
* @param xmiID
|
|
|
|
*/
|
|
|
|
public void writeXmiID(String xmiID) {
|
|
|
|
if (this.docString != null) {
|
|
|
|
this.docString = docString.replaceAll("\\n[\\s]*# PyUML: [^\\n]*", "");
|
|
|
|
|
|
|
|
String lineToInsert ="";
|
|
|
|
lineToInsert += "# PyUML: Do not remove this line! # XMI_ID:" + xmiID+"\n";
|
|
|
|
if (! this.docString.endsWith("\n"))
|
|
|
|
lineToInsert = "\n"+this.indent + lineToInsert + this.indent ;
|
|
|
|
if (! this.docString.startsWith("\n"))
|
|
|
|
this.docString = "\n"+this.indent + this.docString;
|
|
|
|
this.docString = docString + lineToInsert;
|
|
|
|
FileRefactoring.replaceFromCoordinate(this.inFile, this.docStringLine, this.docStringCol+3, this.docString, "\"\"\"", true);
|
|
|
|
}else{
|
|
|
|
this.docString=this.indent+"\"\"\" # PyUML: Do not remove this line! # XMI_ID:" + xmiID + "\"\"\"\n";
|
|
|
|
FileRefactoring.insertAtLine(this.inFile, this.docStringLine + 1, this.docString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the associations in the classes docstring.
|
|
|
|
*/
|
|
|
|
public boolean updateAssociations(Classifier modelClass) throws PyUMLSynchronizeCodeException {
|
|
|
|
if (this.docString != null) {
|
|
|
|
String lineToInsert="";
|
|
|
|
if (modelClass.getAssociations().size() > 0)
|
|
|
|
lineToInsert += "# PyUML: Associations of this class:\n";
|
|
|
|
|
|
|
|
associationsLoop:
|
|
|
|
for (Association as : modelClass.getAssociations()) {
|
|
|
|
String asName = "";
|
|
|
|
if (as.getName() != null && as.getName().length() > 0 )
|
|
|
|
asName = "'" + as.getName()+"' ";
|
|
|
|
List<Type> ends = as.getEndTypes();
|
|
|
|
String otherClass = "self ";
|
|
|
|
String otherEndLabel ="";
|
|
|
|
String ownEndLabel ="";
|
|
|
|
if (as.getMemberEnds().size() >1) {
|
|
|
|
Type end;
|
|
|
|
if (ends.size() > 1) {
|
|
|
|
Type end1 = ends.get(0);
|
|
|
|
Type end2 = ends.get(1);
|
|
|
|
end = (end1.equals(modelClass) ? end2 : end1);
|
|
|
|
otherEndLabel = (end1.equals(modelClass) ? as.getMemberEnds().get(1).getName()
|
|
|
|
: as.getMemberEnds().get(0).getName());
|
|
|
|
ownEndLabel = (! end1.equals(modelClass) ? as.getMemberEnds().get(1).getName()
|
|
|
|
: as.getMemberEnds().get(0).getName());
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
Type end1 = ends.get(0);;
|
|
|
|
Type end2 = as.getMemberEnds().get(1).getType();
|
|
|
|
|
|
|
|
if (end2 == null)
|
|
|
|
end = end1;
|
|
|
|
else
|
|
|
|
end = (end1.equals(modelClass)?end2:end1);
|
|
|
|
otherEndLabel = as.getMemberEnds().get(0).getName();
|
|
|
|
ownEndLabel = as.getMemberEnds().get(1).getName();
|
|
|
|
|
|
|
|
if (end.equals(modelClass)) {
|
|
|
|
// if the only known end of the navigatable association
|
|
|
|
// is this class, ignore this association.
|
|
|
|
continue associationsLoop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (otherEndLabel != null && otherEndLabel.length() > 0)
|
|
|
|
otherEndLabel = " (being '"+otherEndLabel + "')";
|
|
|
|
|
|
|
|
else
|
|
|
|
otherEndLabel = "";
|
|
|
|
|
|
|
|
if (ownEndLabel != null && ownEndLabel.length() > 0)
|
|
|
|
ownEndLabel = "(being '"+ownEndLabel + "') ";
|
|
|
|
else
|
|
|
|
ownEndLabel = "";
|
|
|
|
|
|
|
|
|
|
|
|
String endType = "";
|
|
|
|
if (end instanceof Classifier)
|
|
|
|
endType = "class";
|
|
|
|
else if (end instanceof Interface)
|
|
|
|
endType = "interface";
|
|
|
|
else endType = "model element";
|
|
|
|
|
|
|
|
otherClass = endType+" "+ end.getName()+otherEndLabel+" in package "+ParseHelpers.getModelPackageStructure(end)+" ";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
lineToInsert += this.indent+"# PyUML: Association "+asName+ownEndLabel+"to " +otherClass+"\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String newDocString = this.docString.replaceAll("\\n[\\s]*# PyUML: Association[^\\n]*", "");
|
|
|
|
|
|
|
|
if (! newDocString.endsWith("\n" + this.indent))
|
|
|
|
lineToInsert = "\n" + this.indent + lineToInsert;
|
|
|
|
if (! newDocString.startsWith("\n"))
|
|
|
|
newDocString = "\n" + this.indent + newDocString;
|
|
|
|
newDocString += lineToInsert;
|
|
|
|
|
|
|
|
if (newDocString.equals(this.docString))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
this.docString = newDocString;
|
|
|
|
FileRefactoring.replaceFromCoordinate(this.inFile, this.docStringLine, this.docStringCol+3, this.docString, "\"\"\"", true);
|
|
|
|
|
|
|
|
this.setChanged(this.getPackageStructure()+this.getName()+"-docString", 0);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gets a Set of PythonTreeClasses that are
|
|
|
|
* the superclasses of this class, which are located
|
|
|
|
* in the same project.
|
|
|
|
* This must be called only after a complete initialization
|
|
|
|
* of the Python Tree so that all super classes can be found
|
|
|
|
* in the root node's classNameDict.
|
|
|
|
*/
|
|
|
|
public Set<PythonTreeClass> getGeneralizationsInProject() {
|
|
|
|
|
|
|
|
// try to find PythonTreeClasses from the superclass Strings
|
|
|
|
Set<PythonTreeClass> superClassesInProject = new HashSet<PythonTreeClass>();
|
|
|
|
for (String superClass :this.superClasses) {
|
|
|
|
if (this.getRoot().getClassDict().containsKey(superClass))
|
|
|
|
superClassesInProject.add(this.getRoot().getClassDict().get(superClass));
|
|
|
|
}
|
|
|
|
return superClassesInProject;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gets a Set of PythonTreeClasses that are
|
|
|
|
* the superclasses of this class, which are located
|
|
|
|
* in the same project. -> Uses the model generalizations to find
|
|
|
|
* the general class, not the code.
|
|
|
|
* This must be called only after a complete initialization
|
|
|
|
* of the Python Tree so that all super classes can be found
|
|
|
|
* in the root node's classNameDict.
|
|
|
|
*/
|
|
|
|
public Set<PythonTreeClass> getModelGeneralizationsInProject(Classifier modelClass) {
|
|
|
|
Set<PythonTreeClass> superClassesInProject = new HashSet<PythonTreeClass>();
|
|
|
|
|
|
|
|
for (Relationship rel : modelClass.getRelationships()) {
|
|
|
|
if (rel instanceof Realization) {
|
|
|
|
Realization real = (Realization) rel;
|
|
|
|
if (real.getClients().size() > 0 && real.getClients().get(0) instanceof Classifier) {
|
|
|
|
Classifier client = (Classifier) real.getClients().get(0);
|
|
|
|
String packageStructure = ParseHelpers.getModelPackageStructure(client) + client.getName();
|
|
|
|
PythonTreeClass pyClass = this.getRoot().getClassDict().get(packageStructure);
|
|
|
|
superClassesInProject.add(pyClass);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Classifier superClass : modelClass.getGenerals()) {
|
|
|
|
String packageStructure = ParseHelpers.getModelPackageStructure(superClass) + superClass.getName();
|
|
|
|
PythonTreeClass pyClass = this.getRoot().getClassDict().get(packageStructure);
|
|
|
|
if (pyClass != null)
|
|
|
|
superClassesInProject.add(pyClass);
|
|
|
|
}
|
|
|
|
return superClassesInProject;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* synchronize methods and attributes of the model with this class
|
|
|
|
* This is not done by xmi_id, but elements that changed name are always
|
|
|
|
* deleted and newly created
|
|
|
|
*/
|
|
|
|
public boolean synchronizeModel(NamedElement modelElement){
|
2009-05-12 13:24:34 +02:00
|
|
|
|
|
|
|
/* only classes in packages are supported. If we find a top-level-class,
|
|
|
|
* exit with warning! */
|
|
|
|
if (super.getParent().isRoot()) {
|
|
|
|
if (getRoot().isShowWarnings()) {
|
|
|
|
MessageDialog.openWarning(null, "Error: Top level class detected!",
|
|
|
|
"Please use in model and code *only* classes inside packages!\n" +
|
|
|
|
"The class was: " + this.getInFile().getName());
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2008-02-20 14:27:19 +01:00
|
|
|
// run super method to save corresponding model element
|
|
|
|
super.synchronizeModel(modelElement);
|
|
|
|
Classifier modelClass = (Classifier) modelElement;
|
|
|
|
|
|
|
|
// set this class private in model, if its name starts with "__"
|
|
|
|
// use protected, if class starts with "_" (this is only python convention)
|
|
|
|
if (this.getName().startsWith("__")) {
|
|
|
|
modelClass.setVisibility(VisibilityKind.PRIVATE_LITERAL);
|
|
|
|
} else if (this.getName().startsWith("_")) {
|
|
|
|
modelClass.setVisibility(VisibilityKind.PROTECTED_LITERAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ## process methods and attributes of this class ##
|
|
|
|
|
|
|
|
// create dictionary of methods / classes, so that
|
|
|
|
// a model element can be accessed by name
|
|
|
|
|
|
|
|
Map<String, Operation> modelChildMethods = new Hashtable<String, Operation>();
|
|
|
|
|
|
|
|
EList<Operation> ownedOperations = null;
|
|
|
|
if (modelClass instanceof Class)
|
|
|
|
ownedOperations = ((Class) modelClass).getOwnedOperations();
|
|
|
|
else if (modelClass instanceof Interface)
|
|
|
|
ownedOperations = ((Interface) modelClass).getOwnedOperations();
|
|
|
|
else ownedOperations = new BasicEList<Operation>();
|
|
|
|
|
|
|
|
for (Operation op : ownedOperations) {
|
|
|
|
modelChildMethods.put(op.getName(), op);
|
|
|
|
}
|
|
|
|
List<Operation> processedOperations = new Vector<Operation>();
|
|
|
|
|
|
|
|
Map<String, Property> modelChildProps = new Hashtable<String, Property>();
|
|
|
|
for (Property prop : modelClass.getAttributes()) {
|
|
|
|
modelChildProps.put(prop.getName(), prop);
|
|
|
|
}
|
|
|
|
List<Property> processedProperties = new Vector<Property>();
|
|
|
|
|
|
|
|
// recurse on methods / attributes, if they exist in model;
|
|
|
|
// otherwise create and recurse
|
|
|
|
|
|
|
|
for (PythonTreeMethod pyChildMeth : this.childMethodDict.values()) {
|
|
|
|
if (modelChildMethods.containsKey(pyChildMeth.getName())){
|
|
|
|
// everything is OK, child method with same name found.
|
|
|
|
// method parameters are edited in child element
|
|
|
|
|
|
|
|
// ### Synchronize child ###
|
|
|
|
boolean success = pyChildMeth.synchronizeModel(modelChildMethods.get(pyChildMeth.getName()));
|
|
|
|
if (! success)
|
|
|
|
return false;
|
|
|
|
processedOperations.add(modelChildMethods.get(pyChildMeth.getName()));
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Method does *not* exist -> create it and process children
|
|
|
|
Operation modelNewMethod = null;
|
|
|
|
if (modelClass instanceof Class)
|
|
|
|
modelNewMethod = ((Class) modelClass).createOwnedOperation(pyChildMeth.getName(), null, null);
|
|
|
|
else if (modelClass instanceof Interface)
|
|
|
|
modelNewMethod = ((Interface) modelClass).createOwnedOperation(pyChildMeth.getName(), null, null);
|
|
|
|
|
|
|
|
// ### Synchronize child ###
|
|
|
|
boolean success = pyChildMeth.synchronizeModel(modelNewMethod);
|
|
|
|
if (! success)
|
|
|
|
return false;
|
|
|
|
processedOperations.add(modelNewMethod);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Collection<PythonTreeAttribute> allAttributes = new Vector<PythonTreeAttribute>();
|
|
|
|
allAttributes.addAll(this.childStaticAttrDict.values());
|
|
|
|
allAttributes.addAll(this.childObjectAttrDict.values());
|
|
|
|
for (PythonTreeAttribute pyChildAtt : allAttributes) {
|
|
|
|
if (modelChildProps.containsKey(pyChildAtt.getName())){
|
|
|
|
// everything is OK, child method with same name found.
|
|
|
|
// method parameters are edited in child element
|
|
|
|
|
|
|
|
// ### Synchronize child ###
|
|
|
|
boolean success = pyChildAtt.synchronizeModel(modelChildProps.get(pyChildAtt.getName()));
|
|
|
|
if (! success)
|
|
|
|
return false;
|
|
|
|
processedProperties.add(modelChildProps.get(pyChildAtt.getName()));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Attribute does *not* exist -> create it and process children
|
|
|
|
Property modelNewProp = null;
|
|
|
|
if (modelClass instanceof Class)
|
|
|
|
modelNewProp = ((Class) modelClass).createOwnedAttribute(pyChildAtt.getName(), null);
|
|
|
|
else if (modelClass instanceof Interface)
|
|
|
|
modelNewProp = ((Interface) modelClass).createOwnedAttribute(pyChildAtt.getName(), null);
|
|
|
|
|
|
|
|
// ### Synchronize child ###
|
|
|
|
boolean success = pyChildAtt.synchronizeModel(modelNewProp);
|
|
|
|
if (! success)
|
|
|
|
return false;
|
|
|
|
processedProperties.add(modelNewProp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete all methods / attributes, that do no longer exist in code
|
|
|
|
|
|
|
|
for (Operation op : modelClass.getOperations()) {
|
|
|
|
if (! processedOperations.contains(op))
|
|
|
|
this.getRoot().getModelElementsToDestroy().add(op);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Property prop : modelClass.getAttributes()) {
|
|
|
|
if (! processedProperties.contains(prop))
|
|
|
|
this.getRoot().getModelElementsToDestroy().add(prop);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////// Synchronize Code (see superclass) ///////
|
|
|
|
public boolean synchronizeCode(Classifier modelClass) throws PyUMLSynchronizeCodeException{
|
|
|
|
|
|
|
|
// get and filter Stereotypes of this model class
|
|
|
|
// (only PyUML Stereotypes, as defined in GlobalConstants, are saved)
|
|
|
|
this.usedStereotypes = new Vector<String>();
|
|
|
|
List<EObject> stypes = modelClass.getStereotypeApplications();
|
|
|
|
for (EObject stObject : stypes) {
|
|
|
|
String stereotypeName = stObject.eClass().getName();
|
|
|
|
if (GlobalConstants.getPossibleStereoTypes().contains(stereotypeName)) {
|
|
|
|
this.usedStereotypes.add(stereotypeName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.handleStereoTypes(modelClass);
|
|
|
|
|
|
|
|
// see if class was renamed:
|
|
|
|
if (! this.getName().equals(modelClass.getName())) {
|
|
|
|
// now, the file name is OK, rename the class in python code
|
|
|
|
this.renameClassInCode(modelClass);
|
|
|
|
this.getRoot().getRenamedClasses().add(this.getName());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if class was renamed, and the old class name matches the file name,
|
|
|
|
// rename the file
|
|
|
|
// TODO: Works and can be uncommented,
|
|
|
|
// but generalizations/imports are not
|
|
|
|
// updated, so leaving that commented
|
|
|
|
/*
|
|
|
|
if (this.getRoot().getRenamedClasses().contains(this.inFile.getFilePath().lastSegment().replace(".py", ""))) {
|
|
|
|
IPath newFilePath = this.renameClassFile(modelClass.getName());
|
|
|
|
if (newFilePath != null) {
|
|
|
|
this.inFile.setFilePath(newFilePath.addFileExtension("py"));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throw new PyUMLSynchronizeCodeException();
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
// see if list of superclasses changed
|
|
|
|
boolean superClassesChanged = false;
|
|
|
|
|
|
|
|
List<Realization> interfaceList = new Vector<Realization>();
|
|
|
|
for (Relationship rel : modelClass.getRelationships()) {
|
|
|
|
if (rel instanceof Realization) {
|
|
|
|
interfaceList.add((Realization) rel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (modelClass.getGeneralizations().size() + interfaceList.size() != this.superClasses.size())
|
|
|
|
superClassesChanged =true;
|
|
|
|
else for (Generalization gen : modelClass.getGeneralizations()) {
|
|
|
|
if (! this.superClasses.contains(
|
|
|
|
ParseHelpers.getModelPackageStructure(gen.getGeneral())
|
|
|
|
+ gen.getGeneral().getName())) {
|
|
|
|
superClassesChanged = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if yes, rewrite list of generalizations
|
|
|
|
if (superClassesChanged)
|
|
|
|
this.rewriteGeneralizations(modelClass, interfaceList);
|
|
|
|
|
|
|
|
// if there are non-static parameters, make sure there is a
|
|
|
|
// __init__-method in the class
|
|
|
|
|
|
|
|
boolean hasStaticAttributes = false;
|
|
|
|
boolean hasNonStaticAttributes = false;
|
|
|
|
EList<Property> ownedProperties = null;
|
|
|
|
if (modelClass instanceof Class)
|
|
|
|
ownedProperties = ((Class) modelClass).getOwnedAttributes();
|
|
|
|
else if (modelClass instanceof Interface)
|
|
|
|
ownedProperties = ((Interface) modelClass).getOwnedAttributes();
|
|
|
|
|
|
|
|
for (Property modelProp : ownedProperties) {
|
|
|
|
if (! modelProp.isStatic())
|
|
|
|
hasNonStaticAttributes = true;
|
|
|
|
else
|
|
|
|
hasStaticAttributes = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasNonStaticAttributes && (! childMethodDict.containsKey("__init__"))) {
|
|
|
|
this.createNewMethod("__init__", this.lastLine, false, null);
|
|
|
|
this.getRoot().setChangedFileLine("def __init__(self):"+this.lastLine);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasNonStaticAttributes) {
|
|
|
|
// check the __init__ method; this is not necessarily checked automatically, as
|
|
|
|
// it might be not present in the model
|
|
|
|
boolean changed = PythonTreeMethod.fixAttributeDefinitions(modelClass, childMethodDict.get("__init__").getIndent(), this.childObjectAttrDict, this, childMethodDict.get("__init__").getLastLine(), false);
|
|
|
|
if (changed) return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasStaticAttributes) {
|
|
|
|
// check if all static attributes in model are present in code
|
|
|
|
int insertLine;
|
|
|
|
if (this.astNode.body.length > 0)
|
|
|
|
insertLine = this.astNode.body[0].beginLine + 1;
|
|
|
|
else
|
|
|
|
insertLine = this.lastLine;
|
|
|
|
|
|
|
|
boolean changed = PythonTreeMethod.fixAttributeDefinitions(modelClass, " ", this.childStaticAttrDict, this, insertLine, true);
|
|
|
|
if (changed) return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// iterate over child elements of model
|
|
|
|
|
|
|
|
// save all method names in iteration to detect duplicate method names
|
|
|
|
List<String> childModelOpNames = new Vector<String>();
|
|
|
|
|
|
|
|
for (Element modelChild:modelClass.getOwnedElements()) {
|
|
|
|
//child method found
|
|
|
|
if (modelChild instanceof Operation) {
|
|
|
|
Operation childModelOp = (Operation) modelChild;
|
|
|
|
|
|
|
|
// test if a method with this name already existed in model.
|
|
|
|
// if yes, an exception is thrown -> overloading is not supported in Python
|
|
|
|
if (childModelOpNames.contains(childModelOp.getName())) {
|
|
|
|
throw new PyUMLSynchronizeCodeException("Multiple methods with same name are not\n" +
|
|
|
|
"supported in Python!\n" +
|
|
|
|
"Please correct this error in the UML model!\n" +
|
|
|
|
"Source:\n" +
|
|
|
|
"- Method: "+ childModelOp.getName() +
|
|
|
|
"\n- Class: "+ this.getName() +
|
|
|
|
"\n- Package: "+this.getPackageStructure());
|
|
|
|
|
|
|
|
} else {
|
|
|
|
childModelOpNames.add(childModelOp.getName());
|
|
|
|
}
|
|
|
|
|
|
|
|
PythonTreeMethod childPyOp = null;
|
|
|
|
for (PythonTreeMethod childMeth : this.childMethodDict.values()) {
|
|
|
|
if (childMeth.getName().equals(childModelOp.getName())) {
|
|
|
|
childPyOp = childMeth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (childPyOp != null) {
|
|
|
|
// everything OK, child method with same name found
|
|
|
|
boolean startNew = childPyOp.synchronizeCode(childModelOp);
|
|
|
|
if (startNew) return true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// look if method was existing and was renamed
|
|
|
|
String xmi_id = this.getRoot().getModelXmiDict().get(childModelOp);
|
|
|
|
Map<String, EObject> xmiModelDictOld = this.getRoot().getXmiModelDictOld();
|
|
|
|
if (xmiModelDictOld.containsKey(xmi_id)) {
|
|
|
|
Operation oldOp = (Operation) xmiModelDictOld.get(xmi_id);
|
|
|
|
|
|
|
|
String oldOpClassName = ((NamedElement)oldOp.getOwner()).getName();
|
|
|
|
|
|
|
|
if (oldOpClassName.equals(this.getName())) {
|
|
|
|
String oldName = oldOp.getName();
|
|
|
|
PythonTreeMethod renamedPyOp = null;
|
|
|
|
for (PythonTreeMethod childMeth : this.childMethodDict.values()) {
|
|
|
|
if (childMeth.getName().equals(oldName)) {
|
|
|
|
renamedPyOp = childMeth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (renamedPyOp != null) {
|
|
|
|
// found operation that was renamed.
|
|
|
|
// rename it also in code
|
|
|
|
int line = renamedPyOp.getAstNode().beginLine;
|
|
|
|
int col = renamedPyOp.getAstNode().name.beginColumn;
|
|
|
|
boolean result = BicycleRefactoring.doRenameObject(this.inFile,
|
|
|
|
childModelOp.getName(), line, col, this.getRoot().getProject());
|
|
|
|
if (! result)
|
|
|
|
return false;
|
|
|
|
setChanged(childModelOp.getName(), line);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// else -> was not renamed -> create new method
|
|
|
|
this.createNewMethod(childModelOp.getName(), this.lastLine, childModelOp.isStatic(), childModelOp);
|
|
|
|
setChanged(childModelOp.getName(), this.lastLine);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// else -> was not renamed -> create new method
|
|
|
|
this.createNewMethod(childModelOp.getName(),this.lastLine, childModelOp.isStatic(), childModelOp);
|
|
|
|
setChanged(childModelOp.getName(), this.lastLine);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// else -> was not renamed -> create new method
|
|
|
|
this.createNewMethod(childModelOp.getName(),this.lastLine, childModelOp.isStatic(), childModelOp);
|
|
|
|
setChanged(childModelOp.getName(), this.lastLine);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if imports are correct!
|
|
|
|
boolean changed= this.fixImports(modelClass);
|
|
|
|
if (changed)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
changed= this.updateAssociations(modelClass);
|
|
|
|
if (changed)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Realize all recognized stereotypes;
|
|
|
|
* For the BeanClass Stereotype, this will simply create getter/setter
|
|
|
|
* methods for every non-static attribute in the model, which will then automatically
|
|
|
|
* be realized as code.
|
|
|
|
*/
|
|
|
|
public void handleStereoTypes(Classifier modelClassifier) {
|
|
|
|
|
|
|
|
if (! (modelClassifier instanceof Class)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Class modelClass = (Class) modelClassifier;
|
|
|
|
|
|
|
|
if (this.usedStereotypes.contains(GlobalConstants.STEREOTYPE_BEANCLASS)) {
|
|
|
|
for (Property att : modelClass.getOwnedAttributes()) {
|
|
|
|
String attName = att.getName();
|
|
|
|
// make first letter uppercase for CamelCase bean standard
|
|
|
|
attName = attName.substring(0,1).toUpperCase() + attName.substring(1);
|
|
|
|
|
|
|
|
// add getter/setter
|
|
|
|
|
|
|
|
// check if method already exists
|
|
|
|
boolean getterExists = false;
|
|
|
|
boolean setterExists = false;
|
|
|
|
for (Operation op : modelClass.getOwnedOperations()) {
|
|
|
|
if (op.getName().equals("get"+attName))
|
|
|
|
getterExists = true;
|
|
|
|
if (op.getName().equals("set"+attName))
|
|
|
|
setterExists = true;
|
|
|
|
}
|
|
|
|
if (!getterExists) {
|
|
|
|
Operation op = modelClass.createOwnedOperation("get"+attName, null, null);
|
|
|
|
op.createOwnedParameter("self", null);
|
|
|
|
op.createOwnedComment().setBody("Getter Created by PyUML:"+att.getName());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!setterExists) {
|
|
|
|
Operation op = modelClass.createOwnedOperation("set"+attName, null, null);
|
|
|
|
op.createOwnedParameter("self", null);
|
|
|
|
op.createOwnedParameter("value", null);
|
|
|
|
op.createOwnedComment().setBody("Setter Created by PyUML:"+att.getName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* rename classes using bicycle repair man
|
|
|
|
* @param modelClass
|
|
|
|
* @return success
|
|
|
|
*/
|
|
|
|
public boolean renameClassInCode(Classifier modelClass) throws PyUMLSynchronizeCodeException{
|
|
|
|
// rename Class using Bicycle Repair Man
|
|
|
|
|
|
|
|
return BicycleRefactoring.doRenameObject(
|
|
|
|
this.inFile,
|
|
|
|
modelClass.getName(), this.astNode.name.beginLine,
|
|
|
|
this.astNode.name.beginColumn, this.getRoot().getProject());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Move this class in the python code to another package/folder
|
|
|
|
* * Rename the class file
|
|
|
|
*
|
|
|
|
* @param newPath the IPath where the class is to be moved
|
|
|
|
* @return true on success, false if package already exists
|
|
|
|
*/
|
|
|
|
public boolean moveClass(IPath newPath) {
|
|
|
|
IFile classFile = EclipseHelperMethods.createFile(
|
|
|
|
this.inFile.getFilePath().toOSString(), this.getProject());
|
|
|
|
if (! classFile.exists()) {
|
|
|
|
MessageDialog.openError(null, "Error moving class", "Class " + this.name + " cannot " +
|
|
|
|
"be renamed\n because it does not seem to exist!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
newPath = EclipseHelperMethods.createFile(newPath.toOSString()+".py", this.getProject()).getFullPath();
|
|
|
|
classFile.move(newPath, true, null);
|
|
|
|
this.inFile.setFilePath(this.getProject().getLocation().append(newPath));
|
|
|
|
|
|
|
|
} catch (CoreException e) {
|
|
|
|
MessageDialog.openError(null, "Error moving class", "The class " +this.name+ " cannot" +
|
|
|
|
" be moved to "+ newPath +".\n Propably a class with the new name already exists!" +
|
|
|
|
"\n\nReason:\n" +e.getMessage());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
PythonTreePackage.completeNewStart = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rename this class file in the python code
|
|
|
|
*
|
|
|
|
* @param newName the new name of the class in the same directory
|
|
|
|
* @return the iPath of the created file, null if new class file already exists
|
|
|
|
*/
|
|
|
|
public IPath renameClassFile(String newName) {
|
|
|
|
IFile classFile = EclipseHelperMethods.createFile(this.inFile.getFilePath().toOSString(), this.getProject());
|
|
|
|
IPath newPath = classFile.getLocation().removeLastSegments(1).append(newName);
|
|
|
|
if (this.moveClass(newPath))
|
|
|
|
return newPath;
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add/remove generalizations from class in code
|
|
|
|
* @param modelClass
|
|
|
|
* @return success
|
|
|
|
*/
|
|
|
|
public boolean rewriteGeneralizations(Classifier modelClass, List<Realization> interfaceList) {
|
|
|
|
int line = this.astNode.beginLine;
|
|
|
|
String superClassString = "";
|
|
|
|
|
|
|
|
// write all available generalizations as a comma-separated string
|
|
|
|
for (Generalization gen : modelClass.getGeneralizations()) {
|
|
|
|
if (superClassString.length() > 0)
|
|
|
|
superClassString +=", ";
|
|
|
|
superClassString+= gen.getGeneral().getName();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Realization real :interfaceList) {
|
|
|
|
if (superClassString.length() > 0)
|
|
|
|
superClassString +=", ";
|
|
|
|
superClassString+= real.getClients().get(0).getName();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// write all generalizations that were in code, but are not available
|
|
|
|
// in model (e.g. system classes)
|
|
|
|
for (String gen : this.superClasses) {
|
|
|
|
// if this class is part of project, is should be in model
|
|
|
|
// -> if it is not in model, it is to be removed
|
|
|
|
if (this.getRoot().getClassNameDict().containsValue(
|
|
|
|
gen.substring(gen.lastIndexOf("/")+1)))
|
|
|
|
continue;
|
|
|
|
// otherwise, this generalization has to stay, because it cannot be
|
|
|
|
// present in model
|
|
|
|
if (superClassString.length() > 0)
|
|
|
|
superClassString +=", ";
|
|
|
|
superClassString+= gen.substring(gen.lastIndexOf("/")+1);
|
|
|
|
}
|
|
|
|
// if there were generalizations, surround them by parenthesis
|
|
|
|
if (superClassString.length() > 0) {
|
|
|
|
superClassString = "(" + superClassString + ")";
|
|
|
|
}
|
|
|
|
// replace generalizations in class definition
|
|
|
|
String classDefLine = FileRefactoring.getLine(this.inFile, line);
|
|
|
|
Pattern pattern = Pattern.compile("([\\s]*class[\\s]*[^:\\(]*[\\s]*)(\\(?[^:]*):");
|
|
|
|
Matcher matcher = pattern.matcher(classDefLine);
|
|
|
|
if (! matcher.find()){
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
String newClassDefLine = matcher.group(1) + superClassString + ":";
|
|
|
|
if (! classDefLine.equals(newClassDefLine))
|
|
|
|
FileRefactoring.replaceLine(this.inFile, line, newClassDefLine, true);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* create a new class in a File with the classes name
|
|
|
|
* use all information of the model class
|
|
|
|
*
|
|
|
|
* @param modelClass
|
|
|
|
* @param parentPackage
|
|
|
|
* @return true on success, false if something went wrong
|
|
|
|
*/
|
|
|
|
public static boolean createNewClass(Classifier modelClass, PythonTreePackage parentPackage)
|
|
|
|
throws PyUMLSynchronizeCodeException{
|
|
|
|
String className = modelClass.getName();
|
|
|
|
File newClassFile = new File(parentPackage.getPackageDir().append(className).toOSString() + ".py");
|
|
|
|
|
|
|
|
// create class definition line to insert to file
|
|
|
|
String newClassLine = "";
|
|
|
|
newClassLine += "class " + className;
|
|
|
|
|
|
|
|
// create file
|
|
|
|
if (!newClassFile.exists()) {
|
|
|
|
try {
|
|
|
|
newClassFile.createNewFile();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
throw new PyUMLSynchronizeCodeException();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// if file already exists, append new content
|
|
|
|
newClassLine = ParseHelpers.fileToString(newClassFile, parentPackage.getProject())
|
|
|
|
+ "\n" + newClassLine;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get all superclasses and add them to class definition
|
|
|
|
EList<Classifier> superClasses = modelClass.getGenerals();
|
|
|
|
if (superClasses.size() > 0) {
|
|
|
|
newClassLine = "\n" + newClassLine;
|
|
|
|
newClassLine +="(";
|
|
|
|
for (int i=0; i<superClasses.size(); i++) {
|
|
|
|
Classifier superClass = superClasses.get(i);
|
|
|
|
if (i > 0)
|
|
|
|
newClassLine += ", ";
|
|
|
|
newClassLine += superClass.getName();
|
|
|
|
}
|
|
|
|
newClassLine += ")";
|
|
|
|
}
|
|
|
|
newClassLine += ":";
|
|
|
|
String xmi_id= parentPackage.getRoot().getModelXmiDict().get(modelClass);
|
|
|
|
if (modelClass instanceof Class)
|
|
|
|
newClassLine += "\n \"\"\"\n Class created by PyUML";
|
|
|
|
else
|
|
|
|
newClassLine += "\n \"\"\"\n Interface created by PyUML";
|
|
|
|
newClassLine += "\n # PyUML: Do not remove this line! # XMI_ID:" + xmi_id +
|
|
|
|
"\n \"\"\"";
|
|
|
|
ParseHelpers.stringToFile(newClassFile, newClassLine, parentPackage.getProject());
|
|
|
|
|
|
|
|
parentPackage.getRoot().setChangedFileLine(newClassFile.getAbsolutePath());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks the current import statements to ensure all superclasses
|
|
|
|
* are imported
|
|
|
|
*
|
|
|
|
* @return true, if anything was changed, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean fixImports(Classifier modelClass) throws PyUMLSynchronizeCodeException{
|
2009-01-12 11:41:06 +01:00
|
|
|
return false; // See issues in FIXME below
|
|
|
|
// // iterate over all superclasses in project
|
|
|
|
// Map<String, String> superClassPackages = new HashMap<String, String>();
|
|
|
|
// for (PythonTreeClass superClass : this.getModelGeneralizationsInProject(modelClass)) {
|
|
|
|
//
|
|
|
|
// // get package structure, e.g. mypackage.subpackage.MyClass
|
|
|
|
// String packageDef = "";
|
|
|
|
// PythonTreePackage superPackage = superClass.getParent();
|
|
|
|
// while (superPackage != null && (! (superPackage instanceof PythonTreeRoot))) {
|
|
|
|
// packageDef = superPackage.getName() + "." + packageDef;
|
|
|
|
// superPackage = superPackage.getParent();
|
|
|
|
// }
|
|
|
|
// String superClassModuleName = superClass.inFile.getName().replace(".py", "");
|
|
|
|
// packageDef += superClassModuleName;
|
|
|
|
// superClassPackages.put(superClass.getName(), packageDef);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // find import statements in code
|
|
|
|
// // suppose, that all import statements are at the beginning of the
|
|
|
|
// // file, only white or comment lines can be above them.
|
|
|
|
// String[] fileContent = this.inFile.getFileContent().split("\n");
|
|
|
|
// int lineNo = 0;
|
|
|
|
// String line = "";
|
|
|
|
// if (superClassPackages.size() > 0) {
|
|
|
|
// do {
|
|
|
|
// line = fileContent[lineNo];
|
|
|
|
// String[] importParts = getImport(line);
|
|
|
|
// if (importParts != null){
|
|
|
|
// String className = importParts[0];
|
|
|
|
// String packageDef = importParts[1];
|
|
|
|
// String comment = importParts[2];
|
|
|
|
// // if class is imported, but package changed -> rewrite line!
|
|
|
|
// // FIXME: This is messed up since an import may be done without a "from" statement;
|
|
|
|
// // it might look like "import mypackage" and refer to it by "mypackage.MyClass"
|
|
|
|
// // or even like "import mypackage.mysubpackage as myalias"...
|
|
|
|
// // therefore I disable import fixing as long as this issue has not been solved
|
|
|
|
// // by Jakob
|
|
|
|
// if (superClassPackages.containsKey(className) &&
|
|
|
|
// ( ! packageDef.equals(superClassPackages.get(className)))) {
|
|
|
|
// String newString = "from " + superClassPackages.get(className) + " import " + className + comment;
|
|
|
|
// FileRefactoring.replaceLine(this.inFile, lineNo, newString, true);
|
|
|
|
// this.setChanged(null, lineNo);
|
|
|
|
// return true;
|
|
|
|
// }
|
|
|
|
// if (superClassPackages.containsKey(className) && superClassPackages.get(className).equals(packageDef)) {
|
|
|
|
// // line is OK -> remove them from new-import-to-insert-list
|
|
|
|
// superClassPackages.remove(className);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// lineNo ++;
|
|
|
|
// } while(line.matches("[\\s]*[#]?") || line.matches("from.*import.*")||line.matches("import.*"));
|
|
|
|
//
|
|
|
|
// // now, all lines were analyzed; the needed import, that were not covered
|
|
|
|
// // by a line, must be inserted now!
|
|
|
|
// if (superClassPackages.size() == 0)
|
|
|
|
// return false;
|
|
|
|
//
|
|
|
|
// String insertLines = "";
|
|
|
|
// for (String superClassName : superClassPackages.keySet()) {
|
|
|
|
// String packageLine = superClassPackages.get(superClassName);
|
|
|
|
// insertLines += "from " + packageLine + " import " + superClassName + "\n";
|
|
|
|
// }
|
|
|
|
// // if last line is empty, don't insert new empty line
|
|
|
|
// if (lineNo == 1)
|
|
|
|
// // no empty lines are in fron of the insertion -> insert empty line
|
|
|
|
// insertLines += "\n";
|
|
|
|
// else if (line.length() > 0 && (! fileContent[lineNo-2].matches("^[\\s]*$"))) {
|
|
|
|
// insertLines += "\n";
|
|
|
|
// }
|
|
|
|
// FileRefactoring.insertAtLine(this.inFile, lineNo-1, insertLines);
|
|
|
|
// this.setChanged(null, lineNo);
|
|
|
|
// return true;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a string array containing the class name, package
|
|
|
|
* definition and the trailing comment at the zero, first and second
|
|
|
|
* indices for the given import line.
|
|
|
|
*
|
|
|
|
* @param line The python import line
|
|
|
|
*
|
|
|
|
* @return A string array containing class name and packege definition
|
|
|
|
* or null if not found.
|
|
|
|
*
|
|
|
|
* @see PythonTreeClass#FROM_IMPORT_PATTERN
|
|
|
|
*/
|
|
|
|
private String[] getImport(String line) {
|
|
|
|
Matcher matcher = FROM_IMPORT_PATTERN.matcher(line);
|
|
|
|
if (matcher.find()) {
|
|
|
|
String[] importParts = new String[3];
|
|
|
|
importParts[0] = matcher.group(2);
|
|
|
|
importParts[1] = matcher.group(1);
|
|
|
|
importParts[2] = matcher.group(3);
|
|
|
|
return importParts;
|
2008-02-20 14:27:19 +01:00
|
|
|
}
|
2009-01-12 11:41:06 +01:00
|
|
|
return null;
|
2008-02-20 14:27:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This creates a new method in the current class
|
|
|
|
*
|
|
|
|
* @param name the method's name
|
|
|
|
* @param lastClassLine the last line in code that is part of the class
|
|
|
|
* (or the first line after class -1)
|
|
|
|
* -> this is the line where the method is inserted!
|
|
|
|
* can be -1 if the method should be appended to end of file
|
|
|
|
* @param isStatic if true, method will have a staticmethod call
|
|
|
|
* @param modelOp the corresponding model operation; if there is a comment
|
|
|
|
* indicating that this is a getter/setter, the appropriate method content
|
|
|
|
* is created. Can be null.
|
|
|
|
*/
|
|
|
|
public void createNewMethod(String name, int lastClassLine, boolean isStatic, Operation modelOp) {
|
|
|
|
String newLine;
|
|
|
|
if (isStatic) {
|
|
|
|
newLine = "\n" + this.indent + "def " + name +"():\n \"\"\" Created by PyUML \"\"\"\n"
|
|
|
|
+ this.indent + name + " = staticmethod (" + name + ")\n";
|
|
|
|
} else {
|
|
|
|
newLine = "\n" + this.indent + "def " + name +"(self):\n \"\"\" Created by PyUML \"\"\"\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this is a getter/setter class, put content to method
|
|
|
|
if (modelOp != null && modelOp.getOwnedComments().size() > 0) {
|
|
|
|
String opComment = modelOp.getOwnedComments().get(0).getBody();
|
|
|
|
if (opComment.startsWith("Getter Created by PyUML:")) {
|
|
|
|
String attribute = opComment.replaceAll("Getter Created by PyUML:", "");
|
|
|
|
newLine += " return self."+attribute+"\n";
|
|
|
|
}
|
|
|
|
else if (opComment.startsWith("Setter Created by PyUML:")) {
|
|
|
|
String attribute = opComment.replaceAll("Setter Created by PyUML:", "");
|
|
|
|
newLine += " self."+attribute+" = value\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastClassLine <= 0)
|
|
|
|
FileRefactoring.appendToFile(this.inFile, newLine);
|
|
|
|
else
|
|
|
|
FileRefactoring.insertAtLine(this.inFile, lastClassLine, newLine);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* returns the parent package of this class
|
|
|
|
*/
|
|
|
|
public PythonTreePackage getParent() {
|
|
|
|
return this.inFile.getParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* tells 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 pos Additional information. This is useful, if
|
|
|
|
* line is -1 (append), so that only dead-loops are detected
|
|
|
|
* @param line the changed line in the file
|
|
|
|
*/
|
|
|
|
private void setChanged(String pos, int line) throws PyUMLSynchronizeCodeException{
|
|
|
|
if (pos == null)
|
|
|
|
pos = "";
|
|
|
|
this.getRoot().setChangedFileLine(inFile.getFilePath().toOSString()+":"+line+":"+pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Map<String, PythonTreeMethod> getChildMethodDict() {
|
|
|
|
return childMethodDict;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Map<String, PythonTreeAttribute> getChildStaticAttrDict() {
|
|
|
|
return childStaticAttrDict;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Map<String, PythonTreeAttribute> getChildObjectAttrDict() {
|
|
|
|
return childObjectAttrDict;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setChildStaticAttrDict(
|
|
|
|
Map<String, PythonTreeAttribute> childStaticAttrDict) {
|
|
|
|
this.childStaticAttrDict = childStaticAttrDict;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This creates a string containing the python package structure as
|
|
|
|
* "/packSup/packSub/" , where the last package contains the
|
|
|
|
* current class.
|
|
|
|
* @return a string containing the parent package structure
|
|
|
|
* of this class
|
|
|
|
*/
|
|
|
|
public String getPackageStructure() {
|
|
|
|
PythonTreePackage pack = this.getParent();
|
|
|
|
String structure = "";
|
|
|
|
while (! (pack instanceof PythonTreeRoot))
|
|
|
|
{
|
|
|
|
structure = "/"+pack.getName() + structure ;
|
|
|
|
pack = pack.getParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
return structure + "/";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a complete package structure (including class)
|
|
|
|
* from the class imports for a class (separated by "/"
|
2009-01-12 11:41:06 +01:00
|
|
|
* e.g. "/a/b/c/MyClass" for "MyClass", if there is
|
2008-02-20 14:27:19 +01:00
|
|
|
* an import line "from a.b.c.MyClassFile import MyClass"
|
2009-01-12 11:41:06 +01:00
|
|
|
* or "import a.b.c.MyClassFile" and class name is "MyClassFile.MyClass"
|
|
|
|
* or "import a.b.c.MyClassFile as myalias" and class name is "myalias.MyClass".
|
|
|
|
*
|
|
|
|
* @param className The name of the class too lookup
|
|
|
|
* @return The full package / class structure
|
2008-02-20 14:27:19 +01:00
|
|
|
*/
|
|
|
|
public String getPackFromImports(String className) {
|
2009-01-12 11:41:06 +01:00
|
|
|
search:
|
|
|
|
if (!packageCache.containsKey(className)) {
|
|
|
|
// the fully qualified package name in the format "/mypackage/mysubpackage/MyClass"
|
|
|
|
String packageName = null;
|
|
|
|
String fileContent = this.getInFile().getFileContent();
|
|
|
|
Pattern pattern = Pattern.compile(".*from[\\s]+([^\\s]+)\\.[^\\.\\s]+[\\s]+import[\\s]+" + className + ".*");
|
|
|
|
Matcher matcher = pattern.matcher(fileContent);
|
|
|
|
if (matcher.find()){
|
|
|
|
packageName = "/" + (matcher.group(1) + "." + className).replace(".", "/");
|
|
|
|
packageCache.put(className, packageName);
|
|
|
|
break search;
|
|
|
|
}
|
|
|
|
// lets check for fully qualified package imports
|
|
|
|
// e.g. "import mypackage.myclassfile" and "mypackage.myclassfile.MyClass"
|
|
|
|
// or aliases "import mypackage.mysubpackage.myclassfile as myalias" and "myalias.MyClass"
|
|
|
|
String[] parts = className.split("\\.");
|
|
|
|
if (parts.length > 1) { // there is at least one package, without the classfile
|
|
|
|
String strippedClassName = parts[parts.length - 1];
|
|
|
|
String prefix = join(parts, ".", 0, parts.length - 1);
|
|
|
|
pattern = Pattern.compile(".*import[\\s]+" + prefix + ".*");
|
|
|
|
matcher = pattern.matcher(fileContent);
|
|
|
|
if (matcher.find()) { // we have a full qualified import
|
|
|
|
packageName = "/" + join(parts, "/", 0 , 2) + "/" + strippedClassName;
|
|
|
|
packageCache.put(className, packageName);
|
|
|
|
break search;
|
|
|
|
}
|
|
|
|
pattern = Pattern.compile(".*import[\\s]+([^\\s]+)\\.[^\\.\\s]+[\\s]+as[\\s]+" + prefix + ".*");
|
|
|
|
matcher = pattern.matcher(fileContent);
|
|
|
|
if (matcher.find()) { // we have an alias
|
|
|
|
packageName = "/" + (matcher.group(1) + "." + strippedClassName).replace(".", "/");
|
|
|
|
packageCache.put(className, packageName);
|
|
|
|
break search;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if no import (with class structure in front) was found, we assume that the class
|
|
|
|
// is in the same package
|
|
|
|
packageCache.put(className, this.getPackageStructure() + className);
|
|
|
|
}
|
|
|
|
return packageCache.get(className);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Utility method to create a string from a string array,
|
|
|
|
* joined with the given separator.
|
|
|
|
*
|
|
|
|
* @param parts The string array to get the parts to join from
|
|
|
|
* @param separator The string that glues the parts together
|
|
|
|
* @return The joined string from the given parts and separator
|
|
|
|
*
|
|
|
|
* @see PythonTreeClass#join(String[], String, int, int)
|
|
|
|
*/
|
|
|
|
private String join(String[] parts, String separator) {
|
|
|
|
return join(parts, separator, 0, parts.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Utility method to create a string from a (partial) string array,
|
|
|
|
* joined with the given separator.
|
|
|
|
* <br />
|
|
|
|
* E.g.:
|
|
|
|
* <code>
|
|
|
|
* String abc = join(new String[] { a, b, c }, "/", 0, 3);
|
|
|
|
* </code>
|
|
|
|
* will result in
|
|
|
|
* <code>
|
|
|
|
* "a/b/c"
|
|
|
|
* </code>
|
|
|
|
*
|
|
|
|
* @param parts The string array to get the parts to join from
|
|
|
|
* @param separator The string that glues the parts together
|
|
|
|
* @param offset From where to start in the parts array
|
|
|
|
* @param length How many elements from offset should be joined
|
|
|
|
* @return The joined string from the given parts and separator
|
|
|
|
*/
|
|
|
|
private String join(String[] parts, String separator, int offset, int length) {
|
|
|
|
if (parts.length - offset < length) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
StringBuffer buffer = new StringBuffer();
|
|
|
|
for (int i = offset; i < length; i++) {
|
|
|
|
buffer.append(parts[i]);
|
|
|
|
if (i < length - 1) {
|
|
|
|
buffer.append(separator);
|
|
|
|
}
|
2008-02-20 14:27:19 +01:00
|
|
|
}
|
2009-01-12 11:41:06 +01:00
|
|
|
return buffer.toString();
|
2008-02-20 14:27:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the file where this class is defined
|
|
|
|
*/
|
|
|
|
public PythonTreeFile getInFile() {
|
|
|
|
return inFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
public List<String> getUsedStereotypes() {
|
|
|
|
return usedStereotypes;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getIndent() {
|
|
|
|
return indent;
|
|
|
|
}
|
|
|
|
}
|