FEATURE: Added import support for imports without "from" and with "as" statements
NOTE: I disabled import fixing, to avoid duplicate / wrong imports, should be reviewed and adapted to the new import detection capabilities
This commit is contained in:
parent
bb149cb7f2
commit
19ec7a8c8d
@ -19,6 +19,7 @@ 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;
|
||||
import org.eclipse.gmf.runtime.common.core.util.StringUtil;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.uml2.uml.Association;
|
||||
import org.eclipse.uml2.uml.Class;
|
||||
@ -35,6 +36,7 @@ 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;
|
||||
import org.python.pydev.parser.jython.ast.Break;
|
||||
import org.python.pydev.parser.jython.ast.ClassDef;
|
||||
import org.python.pydev.parser.jython.ast.Expr;
|
||||
import org.python.pydev.parser.jython.ast.FunctionDef;
|
||||
@ -61,7 +63,10 @@ import pyUML.refactoring.FileRefactoring;
|
||||
* - can write XMI ID to classes docstring
|
||||
*
|
||||
*/
|
||||
public class PythonTreeClass extends PythonTreeNode{
|
||||
public class PythonTreeClass extends PythonTreeNode {
|
||||
|
||||
private static final Pattern FROM_IMPORT_PATTERN = Pattern.compile("[\\s]*from[\\s]*([^\\s]*)[\\s]*import[\\s]*([^\\s#]*)([\\s#]?.*)");
|
||||
|
||||
ClassDef astNode;
|
||||
String docString;
|
||||
PythonTreeFile inFile;
|
||||
@ -74,7 +79,16 @@ public class PythonTreeClass extends PythonTreeNode{
|
||||
Map<String, PythonTreeMethod> childMethodDict;
|
||||
Map<String, PythonTreeAttribute> childStaticAttrDict;
|
||||
Map<String, PythonTreeAttribute> childObjectAttrDict;
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Constructor to create a PythonTreeClass of a python class AST
|
||||
* @param parent the PythonTreePackage, which is parent of this class
|
||||
@ -92,13 +106,14 @@ public class PythonTreeClass extends PythonTreeNode{
|
||||
this.childMethodDict = new Hashtable<String, PythonTreeMethod>();
|
||||
this.childStaticAttrDict = new Hashtable<String, PythonTreeAttribute>();
|
||||
this.childObjectAttrDict = new Hashtable<String, PythonTreeAttribute>();
|
||||
packageCache = new HashMap<String, String>();
|
||||
this.initClassNode();
|
||||
this.initSubStructures();
|
||||
|
||||
}
|
||||
|
||||
private void initClassNode() throws PyUMLParseException, PyUMLCancelledException{
|
||||
this.name=((NameTok) this.astNode.name).id;
|
||||
this.name = ((NameTok)this.astNode.name).id;
|
||||
this.docStringLine = this.astNode.beginLine;
|
||||
this.docStringCol = this.astNode.beginColumn;
|
||||
|
||||
@ -131,7 +146,7 @@ public class PythonTreeClass extends PythonTreeNode{
|
||||
PythonTreeRoot root = this.getRoot();
|
||||
|
||||
// test if a class of this name already exists
|
||||
String structuredName = this.getPackageStructure()+this.name;
|
||||
String structuredName = this.getPackageStructure() + this.name;
|
||||
this.getRoot().setSubTaskName(structuredName);
|
||||
|
||||
if (root.getClassDict().containsKey(structuredName) && root.isShowWarnings()) {
|
||||
@ -155,15 +170,16 @@ public class PythonTreeClass extends PythonTreeNode{
|
||||
root.getClassDict().put(structuredName, this);
|
||||
root.getClassNameDict().put(structuredName, this.name);
|
||||
|
||||
for (int i=0; i < this.astNode.bases.length; i++) {
|
||||
for (int i = 0; i < this.astNode.bases.length; i++) {
|
||||
exprType superClass = this.astNode.bases[i];
|
||||
// check whether it's a plain string, which means a classname
|
||||
// or separated by dots
|
||||
if (superClass instanceof Name) {
|
||||
String superName = ((Name)superClass).id;
|
||||
String superClassPackString = this.getPackFromImports(superName);
|
||||
if (superClassPackString != null)
|
||||
this.superClasses.add(superClassPackString);
|
||||
}
|
||||
else if (superClass instanceof Attribute) {
|
||||
this.superClasses.add(superClassPackString);
|
||||
} else if (superClass instanceof Attribute) {
|
||||
// Just extract the content in the paranthesis without
|
||||
// any further analysis
|
||||
// This can be a subclass or a class under a package
|
||||
@ -187,9 +203,13 @@ public class PythonTreeClass extends PythonTreeNode{
|
||||
|
||||
String superClassFullName = line.substring(0, line.indexOf(separator));
|
||||
superClassFullName = superClassFullName.replaceAll("[\\s]", "");
|
||||
|
||||
|
||||
this.superClasses.add(superClassFullName);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -990,75 +1010,105 @@ public class PythonTreeClass extends PythonTreeNode{
|
||||
* @return true, if anything was changed, false otherwise
|
||||
*/
|
||||
public boolean fixImports(Classifier modelClass) throws PyUMLSynchronizeCodeException{
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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];
|
||||
Pattern pattern = Pattern.compile("[\\s]*from[\\s]*([^\\s]*)[\\s]*import[\\s]*([^\\s#]*)([\\s#]?.*)");
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (matcher.find()){
|
||||
String className = matcher.group(2);
|
||||
String packageDef = matcher.group(1);
|
||||
// if class is imported, but package changed -> rewrite line!
|
||||
if (superClassPackages.containsKey(className) &&
|
||||
( ! packageDef.equals(superClassPackages.get(className)))) {
|
||||
String newString = "from " + superClassPackages.get(className) + " import " + className + matcher.group(3);
|
||||
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;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1163,23 +1213,101 @@ public class PythonTreeClass extends PythonTreeNode{
|
||||
/**
|
||||
* Gets a complete package structure (including class)
|
||||
* from the class imports for a class (separated by "/"
|
||||
* e.g. "a/b/c/MyClass" for "MyClass", if there is
|
||||
* e.g. "/a/b/c/MyClass" for "MyClass", if there is
|
||||
* an import line "from a.b.c.MyClassFile import MyClass"
|
||||
* @param className the name of the class
|
||||
* @return the full package/class structure
|
||||
* 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
|
||||
*/
|
||||
public String getPackFromImports(String className) {
|
||||
String fileContent = this.getInFile().getFileContent();
|
||||
Pattern pattern = Pattern.compile(".*from[\\s]+([^\\s]+)\\.[^\\.\\s]+[\\s]+import[\\s]+"+className+".*");
|
||||
Matcher matcher = pattern.matcher(fileContent);
|
||||
|
||||
// if no import (with class structure in front) was found, we assume that the class
|
||||
// is in the same package
|
||||
if (! matcher.find()){
|
||||
return this.getPackageStructure() + className;
|
||||
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);
|
||||
}
|
||||
String result = "/" + (matcher.group(1) + "." + className).replace(".", "/");
|
||||
return result;
|
||||
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);
|
||||
}
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user