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

628 lines
21 KiB
Java
Executable File

package pyUML.pythonTree;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Comment;
import org.eclipse.uml2.uml.Interface;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.VisibilityKind;
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.GlobalConstants;
import pyUML.backend.ParseHelpers;
import pyUML.exceptions.PyUMLParseException;
import pyUML.exceptions.PyUMLSynchronizeCodeException;
import pyUML.refactoring.BicycleRefactoring;
import pyUML.refactoring.FileRefactoring;
/**
* This class represents a Method definition inside a Python Class
*/
public class PythonTreeMethod extends PythonTreeNode {
private FunctionDef astNode;
private boolean staticMethod;
private List<String> arguments;
private String docString;
private int docStringLine;
private int docStringCol;
private int lastLine;
private String indent = " ";
private Dictionary<String, String> argumentDefaultDict;
private Dictionary<String, Integer> argumentLineDict;
private Dictionary<String, Integer> argumentColumnDict;
public PythonTreeMethod(PythonTreeClass parent, FunctionDef astNode, int nextLine) throws PyUMLParseException{
super(parent);
this.astNode = astNode;
this.lastLine = nextLine - 1;
// set last non-empty line as last line of this method
while (this.lastLine>=0 && parent.inFile.getSplittedFileContent()[lastLine-1].matches("[\\s]*")) {
this.lastLine--;
}
this.arguments = new Vector<String>();
this.argumentDefaultDict = new Hashtable<String, String>();
this.argumentLineDict = new Hashtable<String, Integer>();
this.argumentColumnDict = new Hashtable<String, Integer>();
this.initMethod();
}
public void initMethod() throws PyUMLParseException{
this.name = ((NameTok) this.astNode.name).id;
if (this.name.matches(".*__init__.*") && this.getParent().getName().matches(".*CMCEntryBrows.*")) {
}
exprType[] argList = this.astNode.args.args;
exprType[] defaultList = this.astNode.args.defaults;
// ### Get Arguments ### //
// get offset of first argument with default value
int argCount = argList.length;
int defaultCount = defaultList.length;
int defaultOffset = argCount - defaultCount;
// get arguments with corresponding default values
int argNo = 0;
for (exprType arg : argList) {
// fix indent, if needed
String line = this.getParent().getInFile().getSplittedFileContent()[arg.beginLine ];
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 i=0; i< indentLength; i++)
this.indent += " ";
}
// argument name
String argName = ((Name) arg).id;
if (this.arguments.contains(argName)) {
String message = "Argument inconsistency detected!\n"+
"The python method '"+this.name+ "' in the class '" +
this.getParent().getName() +
"'\nhas an argument inconsistency with the argument '" +
argName + "'.\n\n" +
"The argument exists mor than once!\n\n" +
"Please correct this error!";
throw new PyUMLParseException(message);
}
this.arguments.add(argName);
this.argumentLineDict.put(argName, arg.beginLine);
this.argumentColumnDict.put(argName, arg.beginColumn);
// argument default value
if (defaultCount> 0 && argNo >= defaultOffset && (! argName.equals("self"))) {
exprType defaultVal = defaultList[argNo - defaultOffset];
exprType defaultValBefore = null;
// test if parameter without default follows parameter with default
// -> this is not allowed but not caught by pyDev
if (argNo - defaultOffset >= 1)
defaultValBefore = defaultList[argNo - defaultOffset-1];
String defValString = ParseHelpers.getStringOfExpr(defaultVal, this.getParent().getInFile(), false, false, 0);
if (defaultVal == null && defaultValBefore!= null) {
String message = "Argument inconsistency detected!\n"+
"The python method '"+this.name+ "' in the class '" +
this.getParent().getName() +
"'\nhas an argument inconsistency with the argument '" +
argName + "'.\n\n" +
"There are arguments without default values" +
"\nfollowing arguments with default values.\n\n" +
"Please correct this error!";
throw new PyUMLParseException(message);
} else if (defaultVal != null){
this.argumentDefaultDict.put(argName, defValString);
}
}
argNo++;
}
// set static attribute */
if (argList.length == 0 || (! this.arguments.get(0).equals("self")))
this.staticMethod = true;
stmtType[] methodBody = this.astNode.body;
// extract DocString, if it exists
if (methodBody.length > 0 && methodBody[0] instanceof Expr) {
Expr exp = (Expr) methodBody[0];
if (exp.value instanceof Str) {
Str stringExp = (Str) exp.value;
if (stringExp.type == Str.TripleDouble ||
stringExp.type == Str.TripleSingle) {
this.docString=stringExp.s;
this.docStringLine = stringExp.beginLine;
this.docStringCol = stringExp.beginColumn;
}
}
}
}
public boolean synchronizeModel(NamedElement modelElement) {
super.synchronizeModel(modelElement);
Operation modelMethod = (Operation) modelElement;
// set this method private in model, if its name starts with "__"
// use protected, if class starts with "_" (this is only python convention)
if (this.getName().startsWith("__")) {
modelMethod.setVisibility(VisibilityKind.PRIVATE_LITERAL);
} else if (this.getName().startsWith("_")) {
modelMethod.setVisibility(VisibilityKind.PROTECTED_LITERAL);
}
// set/remove static flag
if (this.staticMethod != modelMethod.isStatic())
modelMethod.setIsStatic(this.staticMethod);
List<Parameter> parameterList = modelMethod.getOwnedParameters();
// for all parameters in code -> check if model parameters are the same;
/// if not, add them
for (int i=0; i< this.getArguments().size(); i++) {
String pyArg = this.getArguments().get(i);
if (parameterList.size() > i) {
Parameter modelParam = parameterList.get(i);
// check if parameter name and default value are the same
String defautlValue = (this.getArgumentDefaultDict().get(pyArg));
if (defautlValue == null)
defautlValue = "";
if ((! pyArg.equals(modelParam.getName())) ||
( ! defautlValue.equals(modelParam.getDefault()))) {
// this parameter is not the same!
// remove all parameters from here and create following parameters!
for (int j=i; j< parameterList.size(); j++)
parameterList.remove(j);
// restart loop at the current position (again)
i--;
}
} else {
// Parameter at this position does not exist
// Append new parameter
Parameter newParam = modelMethod.createOwnedParameter(pyArg, null);
newParam.setDefault(this.getArgumentDefaultDict().get(pyArg));
}
}
// test, if there are additional model parameters -> remove them!
if (parameterList.size() > this.getArguments().size()) {
for (int i=this.getArguments().size(); i < parameterList.size(); i++) {
parameterList.get(i).destroy();
}
}
return true;
}
public boolean synchronizeCode(Operation modelOp) throws PyUMLSynchronizeCodeException{
if (this.name.matches("search.*") && this.getParent().getName().startsWith("Group")) {
}
List<String> modelParameters = new Vector<String>();
int line = this.astNode.name.beginLine;
boolean parametersNeedRewrite = false;
boolean modelIsStatic = true;
methodChildLoop:
for (Parameter modelParameter : modelOp.getOwnedParameters()) {
// get all relevant data
String modelParName = modelParameter.getName();
modelParameters.add(modelParName);
// stop if this is not a parameter (null)
if (modelParName == null)
continue methodChildLoop;
if (modelParName.equals("self")) {
modelIsStatic = false;
}
String modelParDefault = modelParameter.getDefault();
if (this.arguments.contains(modelParName)){
//found parameter with same name!
String codeParDefault = this.getArgumentDefaultDict().get(modelParName);
if (((modelParDefault == codeParDefault)&&(codeParDefault== null))
|| (modelParDefault != null && codeParDefault != null
&& (codeParDefault.equals(modelParDefault)
|| (modelParDefault.equals(GlobalConstants.getUnknownExpression())))))
// parameter exists and has the same default value
// everything is fine!
continue methodChildLoop;
// parameter exists, but with different default!
// fix default value!
// if default value does not exist in model
if (modelParDefault == null) {
parametersNeedRewrite = true;
continue methodChildLoop;
}
else if (codeParDefault == null) { //if default value wasn't there
//add a default value
parametersNeedRewrite = true;
continue methodChildLoop;
}
else {
// default value changed
// -> change default value
parametersNeedRewrite = true;
continue methodChildLoop;
}
} else {
// parameter with this name not found!
// look if parameter was renamed (look in old model)
// this is done with bicycle repair man, so that
// all references will be renamed, too
String paramXMIId = this.getRoot().getModelXmiDict().get(modelParameter);
if (this.getRoot().getXmiModelDictOld().keySet().contains(paramXMIId)) {
// parameter found -> try to find old name in code
Parameter oldModelParam = (Parameter) this.getRoot().getXmiModelDictOld().get(paramXMIId);
String oldParamName = oldModelParam.getName();
if (this.arguments.contains(oldParamName)) {
// parameter with old name found -> rename it!
int col = this.argumentColumnDict.get(oldParamName);
BicycleRefactoring.doRenameObject(this.getParent().inFile,
modelParName, line, col, this.getRoot().getProject());
setChanged(line, modelParName);
return true;
}
}
}
//parameter does and did not exist in code
//create it!
parametersNeedRewrite = true;
continue methodChildLoop;
}
// check if parameters were deleted -> delete them in code!
for (String parameter : this.arguments) {
if (! modelParameters.contains(parameter)) {
//parameter does not exist in model -> delete it
parametersNeedRewrite = true;
break;
}
}
// if method changed its static property,
// ensure the "staticmethod" call is added/removed
if (this.fixStaticmethodCall(modelIsStatic))
return true;
if (parametersNeedRewrite) {
// write new parameters definition
String parametersDef = "";
for (Parameter modelParameter: modelOp.getOwnedParameters()) {
// get all relevant data
String modelParName = modelParameter.getName();
// stop if this is not a parameter (null)
if (modelParName == null)
continue;
String modelParDefault = modelParameter.getDefault();
if (parametersDef.length() > 0)
parametersDef += ", ";
parametersDef += modelParName;
if (modelParDefault != null)
parametersDef += "=" + modelParDefault;
}
// replace old definition with help of regex
Pattern pattern = Pattern.compile("([\\s]*def[\\s]*[^\\(]+[\\s]*\\()[^\\)]*(\\)[\\s]*:.*)");
String defLine = FileRefactoring.getLine(this.getParent().inFile, line);
Matcher matcher = pattern.matcher(defLine);
if (! matcher.find()){
return false;
}
// write the new method definition line
String newDefLine = matcher.group(1) + parametersDef + matcher.group(2);
FileRefactoring.replaceLine(this.getParent().inFile, line, newDefLine, true);
setChanged(line, newDefLine);
return true;
}
return false;
}
/**
* On synchronizing code:
* Test the presence of all attributes
* - add/rename them, if needed
* - check initial value and fix it, if needed
*
* @param modelClass the class in the model where the attributes are defined
* @return true, if something was changed; false otherwise
*/
public static boolean fixAttributeDefinitions(
Classifier modelClass, String initIndent,
Map<String, PythonTreeAttribute> presentAttrs,
PythonTreeClass pyClass, int lastLine, boolean isStatic)
throws PyUMLSynchronizeCodeException{
// if this is the __init__ method, ensure all non-static attributes are defined here!
//Map<String, PythonTreeAttribute> presentAttrs = pyClass.getChildObjectAttrDict();
List<Property> neededAttrs = null;
if (modelClass instanceof Class)
neededAttrs = ((Class) modelClass).getOwnedAttributes();
else if (modelClass instanceof Interface)
neededAttrs = ((Interface) modelClass).getOwnedAttributes();
for (Property neededAttr : neededAttrs) {
if (isStatic != neededAttr.isStatic())
continue;
String attrName = neededAttr.getName();
String initialValue = neededAttr.getDefault();
// if initial value was abbreviated before, try to get
// full value out of model
if (initialValue != null && initialValue.endsWith("(...)")) {
for (Comment comment :neededAttr.getOwnedComments()) {
if (comment.getBody().startsWith(GlobalConstants.getFullDefault())) {
initialValue = comment.getBody().substring(GlobalConstants.getFullDefault().length());
break;
}
}
}
if (! presentAttrs.containsKey(attrName)) {
// attribute is not present -> find out, if it was renamed in model
String param_xmi = pyClass.getRoot().getModelXmiDict().get(neededAttr);
if (pyClass.getRoot().getXmiModelDictOld().containsKey(param_xmi)) {
Property oldParameter = (Property) pyClass.getRoot().getXmiModelDictOld().get(param_xmi);
String oldParamName = oldParameter.getName();
if (presentAttrs.containsKey(oldParamName)) {
// attribute was renamed -> rename it in code with Bicycle Repair Man
int line = presentAttrs.get(oldParamName).getLine();
int col = presentAttrs.get(oldParamName).getCol();
boolean success = BicycleRefactoring.doRenameObject(
pyClass.inFile,
attrName, line, col, pyClass.getRoot().getProject());
return success;
}
}
// attribute is and was not present -> create it!
String attrDefLine = (isStatic ? pyClass.getIndent() : initIndent+"self.")+ attrName + " = ";
if (initialValue != null && (! initialValue.endsWith("(...)")))
attrDefLine += initialValue;
else
attrDefLine += "None";
attrDefLine += " # created by PyUML";
if (lastLine <=0 )
FileRefactoring.appendToFile(pyClass.inFile, attrDefLine);
else
FileRefactoring.insertAtLine(pyClass.inFile,
lastLine, attrDefLine + "\n");
pyClass.getRoot().setChangedFileLine(pyClass.inFile.getFilePath().toOSString()+":"+lastLine + attrDefLine + ":");
return true;
} else {
// attribute is defined, check initial value
// if initial model value ends with (...),
// this value was cut when creating the model
// -> do not change it in code
PythonTreeAttribute pyAttr = presentAttrs.get(attrName);
if (pyAttr.getValue() != null && initialValue != null) {
}
if ((!pyAttr.getValue().equals(initialValue))
&& (!(pyAttr.getValue().equals("None") && initialValue==null))
&& (! initialValue.endsWith("(...)"))
&& (! initialValue.startsWith(pyAttr.getValue().replaceAll("\\(\\.\\.\\.\\)$", ""))) ){
// default value changed!
int line = pyAttr.getLine();
Pattern pattern = Pattern.compile("([\\s]*"+(isStatic?"":"self.")+"[^=]*=[\\s]*)([^#]+)(.*)");
String defLine = FileRefactoring.getLine(pyClass.getInFile(), line);
Matcher matcher = pattern.matcher(defLine);
if (! matcher.find()){
return false;
}
String initString = initialValue;
if (initString == null)
initString = "None";
// write the new method definition line
String commentGroup = matcher.group(3);
if (commentGroup.startsWith("#"))
commentGroup = " " + commentGroup;
String newDefLine = matcher.group(1) + initString + commentGroup;
// if this was a multi-line attribute, delete
// all following lines and preserver comments
for (int i = line ; i < pyAttr.getNextLine() - 1; i++)
pyClass.getInFile().setFileContent(FileRefactoring.removeLine(pyClass.inFile, i, false, true));
// now, just replace the changed line
FileRefactoring.replaceLine(pyClass.inFile, line, newDefLine, true);
pyClass.getRoot().setChangedFileLine(pyClass.inFile.getFilePath().toOSString()+":"+line);
return true;
}
}
}
return false;
}
/**
* On synchronizing code:
* Test staticmethod call of this method and
* fix it, if needed
*
* (python static methods must have the call
* <methodname> = staticmethod(<methodname>)
* after the method definition)
*
* @param modelIsStatic true, if this method should be static
* in code and vice versa
* @return true, if something was changed
* false, otherwise
*/
public boolean fixStaticmethodCall(boolean modelIsStatic) throws PyUMLSynchronizeCodeException{
// ensure there is a staticmethod call after the method
String fileContent[] = this.getParent().inFile.getFileContent().split("\n");
String lineContent = "";
int lineNo = this.lastLine;
if (this.lastLine <= 0)
lineNo= fileContent.length -1;
// if next command is "staticmethod", it is meant to be
// part of *this* method
if (fileContent.length > lineNo + 1
&& fileContent[lineNo+1].matches("^[\\s]*[^=\\(\\s]*[\\s]*=[\\s]*staticmethod[\\s]*\\([\\s]*[^\\)\\s]*[\\s]*\\)[\\s]*.*")) {
// the next command is the line to change
lineNo++;
}
// find the last non-empty non-comment line
for (; lineNo>=0; lineNo--){
lineContent = fileContent[lineNo];
if (! (lineContent.matches("^[\\s]*$") || lineContent.matches("^[\\s]*#.*$")) )
break;
}
if (lineContent.matches("^[\\s]*[^=\\(\\s]*[\\s]*=[\\s]*staticmethod[\\s]*\\([\\s]*[^\\)\\s]*[\\s]*\\)[\\s]*.*")) {
// there is already a staticmethod call
if (!modelIsStatic) {
// staticmethod call has to be removed
FileRefactoring.removeLine(this.getParent().inFile, lineNo, true, false);
return true;
}
// ensure staticmethod call is OK
if (lineContent.matches("[\\s]*"+this.getName()+"[\\s]*=[\\s]*staticmethod[\\s]*\\([^\\)\\s]*\\)[\\s]*.*")){
// staticmethod call alright, nothing needs to be changed
return false;
} else {
// fix staticmethod call (e.g. when method was renamed)
String newContent = this.getParent().getIndent() + this.getName()+" = staticmethod("+this.getName()+")";
FileRefactoring.replaceLine(this.getParent().inFile, lineNo+1, newContent, true);
setChanged(lineNo, newContent);
return true;
}
} else {
// there is no staticmethod call at all
if (!modelIsStatic) {
// all is OK
return false;
} else {
//-> insert it after the end of the method
String newContent = this.getParent().getIndent()+this.getName()+" = staticmethod("+this.getName()+")";
lineNo = this.lastLine;
if (this.lastLine <= 0)
lineNo = fileContent.length -1;
// from the next method, find the first before line with whitespace
for (; lineNo>=0; lineNo--){
lineContent = fileContent[lineNo];
if (! (lineContent.matches("^[\\s]*$")))
break;
}
FileRefactoring.insertAtLine(this.getParent().inFile, lineNo + 2, newContent + "\n");
setChanged(lineNo, newContent);
return true;
}
}
}
/**
* 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 line the changed line in the file
*/
private void setChanged(int line, String pos) throws PyUMLSynchronizeCodeException{
if (pos == null)
pos = "";
else pos = ":position:"+pos;
this.getRoot().setChangedFileLine(getParent().inFile.getFilePath().toOSString()+":"+line+pos);
}
public boolean isStatic() {
return staticMethod;
}
public PythonTreeClass getParent() {
return (PythonTreeClass) super.getParent();
}
public String getDocString() {
return docString;
}
public int getDocStringLine() {
return docStringLine;
}
public int getDocStringCol() {
return docStringCol;
}
public FunctionDef getAstNode() {
return astNode;
}
public List<String> getArguments() {
return arguments;
}
public Dictionary<String, String> getArgumentDefaultDict() {
return argumentDefaultDict;
}
public int getLastLine() {
return lastLine;
}
public String getIndent() {
return indent;
}
}