/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.core.docutils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jface.text.IDocument;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.Tuple;
/**
* Class that represents an import found in a document.
*
* @author Fabio
*/
public class ImportHandle {
/**
* Class representing some import information
*
* @author Fabio
*/
public static class ImportHandleInfo {
//spaces* 'from' space+ module space+ import (mod as y)
private static final Pattern FromImportPattern = Pattern
.compile("(from\\s+)(\\.|\\w|\\s|\\\\)+((\\\\|\\s)+import(\\\\|\\s)+)");
private static final Pattern BadFromPattern = Pattern
.compile("from\\s+(\\.|\\w|\\s|\\\\)+(\\\\|\\s)+import");
private static final Pattern ImportPattern = Pattern.compile("(import\\s+)");
/**
* Holds the 'KKK' if the import is from KKK import YYY
* If it's not a From Import, it should be null.
*/
private String fromStr;
/**
* This is the alias that's been imported. E.g.: in from KKK import YYY, ZZZ, this is a list
* with YYY and ZZZ
*/
private List<String> importedStr;
/**
* Comments (one for each imported string) E.g.: in from KKK import (YYY, #comment\n ZZZ), this is a list
* with #comment and an empty string.
*/
private List<String> importedStrComments;
/**
* Starting line for this import.
*/
private int startLine;
/**
* Ending line for this import.
*/
private int endLine;
/**
* Holds whether the import started in the middle of the line (after a ';')
*/
private boolean startedInMiddleOfLine;
/**
* Constructor that does not set the line for the import.
*/
public ImportHandleInfo(String importFound) throws ImportNotRecognizedException {
this(importFound, -1, -1, false, false);
}
/**
* Constructor.
*
* Creates the information to be returned later
*
* @param importFound
* @throws ImportNotRecognizedException
*/
public ImportHandleInfo(String importFound, int lineStart, int lineEnd, boolean startedInMiddleOfLine,
boolean allowBadInput)
throws ImportNotRecognizedException {
this.startLine = lineStart;
this.endLine = lineEnd;
this.startedInMiddleOfLine = startedInMiddleOfLine;
importFound = importFound.trim();
if (importFound.length() == 0) {
throw new ImportNotRecognizedException("Could not recognize empty string as import");
}
char firstChar = importFound.charAt(0);
if (firstChar == 'f') {
//from import
Matcher matcher = FromImportPattern.matcher(importFound);
if (matcher.find()) {
this.fromStr = importFound.substring(matcher.end(1), matcher.end(2)).trim();
//we have to do that because the last group will only have the last match in the string
String importedStr = importFound.substring(matcher.end(3), importFound.length()).trim();
buildImportedList(importedStr);
} else {
if (allowBadInput &&
("from".equals(importFound)
|| BadFromPattern.matcher(importFound).matches())) {
dummyImportList();
return;
}
throw new ImportNotRecognizedException("Could not recognize import: " + importFound);
}
} else if (firstChar == 'i') {
//regular import
Matcher matcher = ImportPattern.matcher(importFound);
if (matcher.find()) {
//we have to do that because the last group will only have the last match in the string
String importedStr = importFound.substring(matcher.end(1), importFound.length()).trim();
buildImportedList(importedStr);
} else {
if (allowBadInput && "import".equals(importFound)) {
dummyImportList();
return;
}
throw new ImportNotRecognizedException("Could not recognize import: " + importFound);
}
} else {
throw new ImportNotRecognizedException("Could not recognize import: " + importFound);
}
}
private void dummyImportList() {
this.importedStr = this.importedStrComments = Arrays.asList(new String[0]);
}
/**
* Fills the importedStrComments and importedStr given the importedStr passed
*
* @param importedStr string with the tokens imported in an import
*/
private void buildImportedList(String importedStr) {
ArrayList<String> lst = new ArrayList<String>();
ArrayList<String> importComments = new ArrayList<String>();
FastStringBuffer alias = new FastStringBuffer();
FastStringBuffer comments = new FastStringBuffer();
for (int i = 0; i < importedStr.length(); i++) {
char c = importedStr.charAt(i);
if (c == '#') {
comments = comments.clear();
i = ParsingUtils.create(importedStr).eatComments(comments, i);
addImportAlias(lst, importComments, alias, comments.toString());
alias = alias.clear();
} else if (c == ',' || c == '\r' || c == '\n') {
addImportAlias(lst, importComments, alias, "");
alias = alias.clear();
} else if (c == '(' || c == ')' || c == '\\') {
//do nothing
// commented out: we'll get the xxx as yyy all in the alias. Clients may treat it separately if needed.
// }else if(c == ' ' || c == '\t'){
//
// String curr = alias.toString();
// if(curr.endsWith(" as") | curr.endsWith("\tas")){
// alias = new StringBuffer();
// }
// alias.append(c);
} else {
alias.append(c);
}
}
if (alias.length() > 0) {
addImportAlias(lst, importComments, alias, "");
}
this.importedStrComments = importComments;
this.importedStr = lst;
}
/**
* Adds an import and its related comment to the given lists (if there's actually something available to be
* added)
*
* @param lst list where the alias will be added
* @param importComments list where the comment will be added
* @param alias the name of the import to be added
* @param importComment the comment related to the import
*/
private void addImportAlias(ArrayList<String> lst, ArrayList<String> importComments, FastStringBuffer alias,
String importComment) {
String aliasStr = alias.toString().trim();
importComment = importComment.trim();
if (aliasStr.length() > 0) {
lst.add(aliasStr);
importComments.add(importComment);
} else if (importComment.length() > 0 && importComments.size() > 0) {
importComments.set(importComments.size() - 1, importComment);
}
}
/**
* @return the from module in the import (may be null).
*/
public String getFromImportStr() {
return this.fromStr;
}
/**
* @return the from module in the import without chars which may appear in the from but should not be considered
* (such as \ and spaces)
*/
public String getFromImportStrWithoutUnwantedChars() {
if (this.fromStr != null) {
return CHARS_NOT_CONSIDERED_IN_IMPORT_INFO.matcher(this.fromStr).replaceAll("");
}
return this.fromStr;
}
/**
* @return the tokens imported from the module (or the alias if it's specified)
*/
public List<String> getImportedStr() {
return this.importedStr;
}
/**
* @return a list with a string for each imported token correspondent to a comment related to that import.
*/
public List<String> getCommentsForImports() {
return this.importedStrComments;
}
/**
* @return the start line for this import (0-based)
*/
public int getStartLine() {
return this.startLine;
}
/**
* @return the end line for this import (0-based)
*/
public int getEndLine() {
return this.endLine;
}
/**
* @return true if this import was started in the middle of the line. I.e.: after a ';'
*/
public boolean getStartedInMiddleOfLine() {
return this.startedInMiddleOfLine;
}
/**
* @return a list of tuples with the iported string and the comment that's attached to it.
*/
public List<Tuple<String, String>> getImportedStrAndComments() {
ArrayList<Tuple<String, String>> lst = new ArrayList<Tuple<String, String>>();
for (int i = 0; i < this.importedStr.size(); i++) {
lst.add(new Tuple<String, String>(this.importedStr.get(i), this.importedStrComments.get(i)));
}
return lst;
}
}
/**
* Document where the import was found
*/
public final IDocument doc;
/**
* The import string found. Note: it may contain comments and multi-lines.
*/
public final String importFound;
/**
* The initial line where the import was found
*/
public final int startFoundLine;
/**
* The final line where the import was found
*/
public int endFoundLine;
/**
* Import information for the import found and handled in this class (only created on request)
*/
private final List<ImportHandleInfo> importInfo;
private final boolean allowBadInput;
/**
* Constructor.
*
* Assigns parameters to fields.
* @param allowBadInput
* @throws ImportNotRecognizedException
*/
public ImportHandle(IDocument doc, String importFound, int startFoundLine, int endFoundLine, boolean allowBadInput)
throws ImportNotRecognizedException {
this.doc = doc;
this.importFound = importFound;
this.startFoundLine = startFoundLine;
this.endFoundLine = endFoundLine;
this.importInfo = new ArrayList<ImportHandleInfo>();
this.allowBadInput = allowBadInput;
int line = startFoundLine;
boolean startedInMiddle = false;
FastStringBuffer imp = new FastStringBuffer();
ImportHandleInfo found = null;
for (int i = 0; i < importFound.length(); i++) {
char c = importFound.charAt(i);
if (c == '#') {
i = ParsingUtils.create(importFound).eatComments(imp, i);
} else if (c == ';') {
String impStr = imp.toString();
int endLine = line + StringUtils.countLineBreaks(impStr);
found = new ImportHandleInfo(impStr, line, endLine, startedInMiddle, allowBadInput);
this.importInfo.add(found);
line = endLine;
imp = imp.clear();
startedInMiddle = true;
} else {
if (c == '\r' || c == '\n') {
startedInMiddle = false;
}
imp.append(c);
}
}
String impStr = imp.toString();
if (impStr.trim().length() > 0) {
this.importInfo.add(new ImportHandleInfo(impStr, line, line + StringUtils.countLineBreaks(impStr),
startedInMiddle, allowBadInput));
}
}
public ImportHandle(IDocument doc, String importFound, int startFoundLine, int endFoundLine)
throws ImportNotRecognizedException {
this(doc, importFound, startFoundLine, endFoundLine, false);
}
private static Pattern CHARS_NOT_CONSIDERED_IN_IMPORT_INFO = Pattern.compile("\\s|\\\\");
/**
* @param realImportHandleInfo the import to match. Note that only a single import statement may be passed as a parameter.
*
* @return true if the passed import matches the import in this handle (note: as this class can actually wrap more
* than 1 import, it'll return true if any of the internal imports match the passed import)
* @throws ImportNotRecognizedException if the passed import could not be recognized
*/
public boolean contains(ImportHandleInfo otherImportInfo) throws ImportNotRecognizedException {
List<ImportHandleInfo> importHandleInfo = this.getImportInfo();
for (ImportHandleInfo info : importHandleInfo) {
if (info.fromStr != otherImportInfo.fromStr) {
String thisFromStr = info.getFromImportStrWithoutUnwantedChars();
String otherImportInfoFromStr = otherImportInfo.getFromImportStrWithoutUnwantedChars();
if (otherImportInfoFromStr == null || thisFromStr == null) {
continue; //keep on to the next possible match
}
if (!otherImportInfoFromStr.equals(thisFromStr)) {
continue; //keep on to the next possible match
}
}
if (otherImportInfo.importedStr.size() != 1) {
continue;
}
if (info.importedStr.contains(otherImportInfo.importedStr.get(0))) {
return true;
}
}
return false;
}
/**
* @return a list with the import information generated from the import this handle is wrapping.
*/
public List<ImportHandleInfo> getImportInfo() {
return this.importInfo;
}
}