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:
jakob_oswald 2009-01-12 10:41:06 +00:00
parent bb149cb7f2
commit 19ec7a8c8d
1 changed files with 220 additions and 92 deletions

View File

@ -19,6 +19,7 @@ import org.eclipse.core.runtime.IPath;
import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EObject;
import org.eclipse.gmf.runtime.common.core.util.StringUtil;
import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.uml2.uml.Association; import org.eclipse.uml2.uml.Association;
import org.eclipse.uml2.uml.Class; import org.eclipse.uml2.uml.Class;
@ -35,6 +36,7 @@ import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.VisibilityKind; import org.eclipse.uml2.uml.VisibilityKind;
import org.python.pydev.parser.jython.ast.Assign; import org.python.pydev.parser.jython.ast.Assign;
import org.python.pydev.parser.jython.ast.Attribute; 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.ClassDef;
import org.python.pydev.parser.jython.ast.Expr; import org.python.pydev.parser.jython.ast.Expr;
import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.FunctionDef;
@ -61,7 +63,10 @@ import pyUML.refactoring.FileRefactoring;
* - can write XMI ID to classes docstring * - 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; ClassDef astNode;
String docString; String docString;
PythonTreeFile inFile; PythonTreeFile inFile;
@ -74,7 +79,16 @@ public class PythonTreeClass extends PythonTreeNode{
Map<String, PythonTreeMethod> childMethodDict; Map<String, PythonTreeMethod> childMethodDict;
Map<String, PythonTreeAttribute> childStaticAttrDict; Map<String, PythonTreeAttribute> childStaticAttrDict;
Map<String, PythonTreeAttribute> childObjectAttrDict; 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 * Constructor to create a PythonTreeClass of a python class AST
* @param parent the PythonTreePackage, which is parent of this class * @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.childMethodDict = new Hashtable<String, PythonTreeMethod>();
this.childStaticAttrDict = new Hashtable<String, PythonTreeAttribute>(); this.childStaticAttrDict = new Hashtable<String, PythonTreeAttribute>();
this.childObjectAttrDict = new Hashtable<String, PythonTreeAttribute>(); this.childObjectAttrDict = new Hashtable<String, PythonTreeAttribute>();
packageCache = new HashMap<String, String>();
this.initClassNode(); this.initClassNode();
this.initSubStructures(); this.initSubStructures();
} }
private void initClassNode() throws PyUMLParseException, PyUMLCancelledException{ 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.docStringLine = this.astNode.beginLine;
this.docStringCol = this.astNode.beginColumn; this.docStringCol = this.astNode.beginColumn;
@ -131,7 +146,7 @@ public class PythonTreeClass extends PythonTreeNode{
PythonTreeRoot root = this.getRoot(); PythonTreeRoot root = this.getRoot();
// test if a class of this name already exists // 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); this.getRoot().setSubTaskName(structuredName);
if (root.getClassDict().containsKey(structuredName) && root.isShowWarnings()) { if (root.getClassDict().containsKey(structuredName) && root.isShowWarnings()) {
@ -155,15 +170,16 @@ public class PythonTreeClass extends PythonTreeNode{
root.getClassDict().put(structuredName, this); root.getClassDict().put(structuredName, this);
root.getClassNameDict().put(structuredName, this.name); 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]; 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) { if (superClass instanceof Name) {
String superName = ((Name)superClass).id; String superName = ((Name)superClass).id;
String superClassPackString = this.getPackFromImports(superName); String superClassPackString = this.getPackFromImports(superName);
if (superClassPackString != null) if (superClassPackString != null)
this.superClasses.add(superClassPackString); this.superClasses.add(superClassPackString);
} } else if (superClass instanceof Attribute) {
else if (superClass instanceof Attribute) {
// Just extract the content in the paranthesis without // Just extract the content in the paranthesis without
// any further analysis // any further analysis
// This can be a subclass or a class under a package // 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)); String superClassFullName = line.substring(0, line.indexOf(separator));
superClassFullName = superClassFullName.replaceAll("[\\s]", ""); superClassFullName = superClassFullName.replaceAll("[\\s]", "");
// currently we suppose that the class will be prefixed by
// either the fully qualified package name or an alias,
this.superClasses.add(superClassFullName); // 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 * @return true, if anything was changed, false otherwise
*/ */
public boolean fixImports(Classifier modelClass) throws PyUMLSynchronizeCodeException{ public boolean fixImports(Classifier modelClass) throws PyUMLSynchronizeCodeException{
// iterate over all superclasses in project return false; // See issues in FIXME below
Map<String, String> superClassPackages = new HashMap<String, String>(); // // iterate over all superclasses in project
for (PythonTreeClass superClass : this.getModelGeneralizationsInProject(modelClass)) { // Map<String, String> superClassPackages = new HashMap<String, String>();
// for (PythonTreeClass superClass : this.getModelGeneralizationsInProject(modelClass)) {
// get package structure, e.g. mypackage.subpackage.MyClass //
String packageDef = ""; // // get package structure, e.g. mypackage.subpackage.MyClass
PythonTreePackage superPackage = superClass.getParent(); // String packageDef = "";
while (superPackage != null && (! (superPackage instanceof PythonTreeRoot))) { // PythonTreePackage superPackage = superClass.getParent();
packageDef = superPackage.getName() + "." + packageDef; // while (superPackage != null && (! (superPackage instanceof PythonTreeRoot))) {
superPackage = superPackage.getParent(); // packageDef = superPackage.getName() + "." + packageDef;
} // superPackage = superPackage.getParent();
String superClassModuleName = superClass.inFile.getName().replace(".py", ""); // }
packageDef += superClassModuleName; // String superClassModuleName = superClass.inFile.getName().replace(".py", "");
superClassPackages.put(superClass.getName(), packageDef); // 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;
} }
return null;
// 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;
} }
/** /**
@ -1163,23 +1213,101 @@ public class PythonTreeClass extends PythonTreeNode{
/** /**
* Gets a complete package structure (including class) * Gets a complete package structure (including class)
* from the class imports for a class (separated by "/" * 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" * an import line "from a.b.c.MyClassFile import MyClass"
* @param className the name of the class * or "import a.b.c.MyClassFile" and class name is "MyClassFile.MyClass"
* @return the full package/class structure * 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) { public String getPackFromImports(String className) {
String fileContent = this.getInFile().getFileContent(); search:
Pattern pattern = Pattern.compile(".*from[\\s]+([^\\s]+)\\.[^\\.\\s]+[\\s]+import[\\s]+"+className+".*"); if (!packageCache.containsKey(className)) {
Matcher matcher = pattern.matcher(fileContent); // the fully qualified package name in the format "/mypackage/mysubpackage/MyClass"
String packageName = null;
// if no import (with class structure in front) was found, we assume that the class String fileContent = this.getInFile().getFileContent();
// is in the same package Pattern pattern = Pattern.compile(".*from[\\s]+([^\\s]+)\\.[^\\.\\s]+[\\s]+import[\\s]+" + className + ".*");
if (! matcher.find()){ Matcher matcher = pattern.matcher(fileContent);
return this.getPackageStructure() + className; 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 packageCache.get(className);
return result; }
/**
* 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();
} }
/** /**