/**
* Copyright (c) 2005-2011 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.
*/
/*
* Created on 23/07/2005
*/
package com.python.pydev.analysis.messages;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.IToken;
import org.python.pydev.core.docutils.StringUtils;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.editor.codecompletion.revisited.visitors.AbstractVisitor;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Import;
import org.python.pydev.parser.jython.ast.ImportFrom;
import org.python.pydev.parser.jython.ast.NameTok;
import org.python.pydev.parser.jython.ast.aliasType;
import com.aptana.shared_core.string.FastStringBuffer;
import com.python.pydev.analysis.IAnalysisPreferences;
public abstract class AbstractMessage implements IMessage {
public static final Map<Integer, String> messages = new HashMap<Integer, String>();
private final int type;
private final int severity;
private IToken generator;
private List<String> additionalInfo;
private int startLine = -1;
/**
* @param generator needed to get the lines/cols for the message (an alternate constructor is given if
* it's already known).
*/
public AbstractMessage(int type, IToken generator, IAnalysisPreferences prefs) {
this.severity = prefs.getSeverityForType(type);
this.type = type;
this.generator = generator;
try {
Assert.isNotNull(generator);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param startLine starts at 1
* @param endLine starts at 1
* @param startCol starts at 1
* @param endCol starts at 1
*/
public AbstractMessage(int type, int startLine, int endLine, int startCol, int endCol, IAnalysisPreferences prefs) {
this.severity = prefs.getSeverityForType(type);
this.type = type;
Assert.isTrue(startLine >= 0);
Assert.isTrue(startCol >= 0);
this.startLine = startLine;
this.startCol = startCol;
this.endLine = endLine;
this.endCol = endCol;
}
private String getTypeStr() {
if (messages.size() == 0) {
messages.put(IAnalysisPreferences.TYPE_UNUSED_IMPORT, "Unused import: %s");
messages.put(IAnalysisPreferences.TYPE_UNUSED_WILD_IMPORT, "Unused in wild import: %s");
messages.put(IAnalysisPreferences.TYPE_UNUSED_VARIABLE, "Unused variable: %s");
messages.put(IAnalysisPreferences.TYPE_UNUSED_PARAMETER, "Unused parameter: %s");
messages.put(IAnalysisPreferences.TYPE_UNDEFINED_VARIABLE, "Undefined variable: %s");
messages.put(IAnalysisPreferences.TYPE_DUPLICATED_SIGNATURE, "Duplicated signature: %s");
messages.put(IAnalysisPreferences.TYPE_REIMPORT, "Import redefinition: %s");
messages.put(IAnalysisPreferences.TYPE_UNRESOLVED_IMPORT, "Unresolved import: %s");
messages.put(IAnalysisPreferences.TYPE_NO_SELF, "Method '%s' should have %s as first parameter");
messages.put(IAnalysisPreferences.TYPE_UNDEFINED_IMPORT_VARIABLE, "Undefined variable from import: %s");
messages.put(IAnalysisPreferences.TYPE_NO_EFFECT_STMT, "Statement apppears to have no effect");
messages.put(IAnalysisPreferences.TYPE_INDENTATION_PROBLEM, "%s");
messages.put(IAnalysisPreferences.TYPE_ASSIGNMENT_TO_BUILT_IN_SYMBOL,
"Assignment to reserved built-in symbol: %s");
messages.put(IAnalysisPreferences.TYPE_PEP8, "%s");
messages.put(IAnalysisPreferences.TYPE_ARGUMENTS_MISATCH, "%s");
}
return messages.get(getType());
}
public int getSeverity() {
return severity;
}
public int getType() {
return type;
}
public int getStartLine(IDocument doc) {
if (startLine < 0) {
startLine = getStartLine(generator, doc);
}
return startLine;
}
/**
* @return the starting line fro the given token (starting at 1)
*/
public static int getStartLine(IToken generator, IDocument doc) {
return getStartLine(generator, doc, generator.getRepresentation());
}
public static int getStartLine(IToken generator, IDocument doc, String shortMessage) {
return getStartLine(generator, doc, shortMessage, false);
}
public static int getStartLine(IToken generator, IDocument doc, String shortMessage, boolean returnAsName) {
if (!generator.isImport()) {
return generator.getLineDefinition();
}
//ok, it is an import... (can only be a source token)
SourceToken s = (SourceToken) generator;
SimpleNode ast = s.getAst();
if (ast instanceof ImportFrom) {
ImportFrom i = (ImportFrom) ast;
//if it is a wild import, it starts on the module name
if (AbstractVisitor.isWildImport(i)) {
return i.module.beginLine;
} else {
//no wild import, let's check the 'as name'
return getNameForRepresentation(i, shortMessage, returnAsName).beginLine;
}
} else if (ast instanceof Import) {
return getNameForRepresentation(ast, shortMessage, returnAsName).beginLine;
} else {
throw new RuntimeException("It is not an import");
}
}
int startCol = -1;
/**
* gets the start col of the message (starting at 1)
*
* @see com.python.pydev.analysis.messages.IMessage#getStartCol(org.eclipse.jface.text.IDocument)
*/
public int getStartCol(IDocument doc) {
if (startCol >= 0) {
return startCol;
}
startCol = getStartCol(generator, doc, getShortMessageStr());
return startCol;
}
private String getShortMessageStr() {
Object msg = getShortMessage();
if (msg instanceof Object[]) {
Object[] msgs = (Object[]) msg;
FastStringBuffer buffer = new FastStringBuffer();
for (Object o : msgs) {
buffer.append(o.toString());
}
return buffer.toString();
} else {
return msg.toString();
}
}
/**
* @return the starting column for the given token (1-based)
*/
public static int getStartCol(IToken generator, IDocument doc) {
return getStartCol(generator, doc, generator.getRepresentation());
}
/**
* @return the starting column for the given token (1-based)
*/
public static int getStartCol(IToken generator, IDocument doc, String shortMessage) {
return getStartCol(generator, doc, shortMessage, false);
}
/**
* @return the starting column for the given token (1-based)
*/
public static int getStartCol(IToken generator, IDocument doc, String shortMessage, boolean returnAsName) {
//not import...
if (!generator.isImport()) {
return generator.getColDefinition();
}
//ok, it is an import... (can only be a source token)
SourceToken s = (SourceToken) generator;
SimpleNode ast = s.getAst();
if (ast instanceof ImportFrom) {
ImportFrom i = (ImportFrom) ast;
//if it is a wild import, it starts on the module name
if (AbstractVisitor.isWildImport(i)) {
return i.module.beginColumn;
} else {
//no wild import, let's check the 'as name'
return getNameForRepresentation(i, shortMessage, returnAsName).beginColumn;
}
} else if (ast instanceof Import) {
return getNameForRepresentation(ast, shortMessage, returnAsName).beginColumn;
} else {
throw new RuntimeException("It is not an import");
}
}
/**
* @param imp this is the import ast
* @param fullRep this is the representation we are looking for
* @param returnAsName defines if we should return the asname or only the name (depending on what we are
* analyzing -- the start or the end of the representation).
*
* @return the name tok for the representation in a given import
*/
private static NameTok getNameForRepresentation(SimpleNode imp, String rep, boolean returnAsName) {
aliasType[] names;
if (imp instanceof Import) {
names = ((Import) imp).names;
} else if (imp instanceof ImportFrom) {
names = ((ImportFrom) imp).names;
} else {
throw new RuntimeException("import expected");
}
for (aliasType alias : names) {
if (alias.asname != null) {
if (((NameTok) alias.asname).id.equals(rep)) {
if (returnAsName) {
return (NameTok) alias.asname;
} else {
return (NameTok) alias.name;
}
}
} else { //let's check for the name
String fullRepNameId = ((NameTok) alias.name).id;
//we have to get all representations, since an import such as import os.path would
//have to match os and os.path
for (String repId : new FullRepIterable(fullRepNameId)) {
if (repId.equals(rep)) {
return (NameTok) alias.name;
}
}
}
}
return null;
}
/**
* @see com.python.pydev.analysis.messages.IMessage#getEndLine(org.eclipse.jface.text.IDocument)
*/
int endLine = -1;
public int getEndLine(IDocument doc) {
return getEndLine(doc, true);
}
public int getEndLine(IDocument doc, boolean getOnlyToFirstDot) {
if (endLine < 0) {
endLine = getEndLine(generator, doc, getOnlyToFirstDot);
}
return endLine;
}
public static int getEndLine(IToken generator, IDocument doc, boolean getOnlyToFirstDot) {
if (generator instanceof SourceToken) {
if (!generator.isImport()) {
return ((SourceToken) generator).getLineEnd(getOnlyToFirstDot);
}
return getStartLine(generator, doc); //for an import, the endline == startline
} else {
return -1;
}
}
int endCol = -1;
public int getEndCol(IDocument doc) {
return getEndCol(doc, true);
}
public int getEndCol(IDocument doc, boolean getOnlyToFirstDot) {
if (endCol >= 0) {
return endCol;
}
endCol = getEndCol(generator, doc, getShortMessageStr(), getOnlyToFirstDot);
return endCol;
}
/**
* @param generator is the token that generated this message
* @param doc is the document where this message will be put
* @param shortMessage is used when it is an import ( = foundTok.getRepresentation())
*
* @return the end column for this message
*
* @see com.python.pydev.analysis.messages.IMessage#getEndCol(org.eclipse.jface.text.IDocument)
*/
public static int getEndCol(IToken generator, IDocument doc, String shortMessage, boolean getOnlyToFirstDot) {
int endCol = -1;
if (generator.isImport()) {
//ok, it is an import... (can only be a source token)
SourceToken s = (SourceToken) generator;
SimpleNode ast = s.getAst();
if (ast instanceof ImportFrom) {
ImportFrom i = (ImportFrom) ast;
//ok, now, this depends on the name
NameTok it = getNameForRepresentation(i, shortMessage, true);
if (it != null) {
endCol = it.beginColumn + shortMessage.length();
return endCol;
}
//if still not returned, it is a wild import... find the '*'
try {
IRegion lineInformation = doc.getLineInformation(i.module.beginLine - 1);
//ok, we have the line... now, let's find the absolute offset
int absolute = lineInformation.getOffset() + i.module.beginColumn - 1;
while (doc.getChar(absolute) != '*') {
absolute++;
}
int absoluteCol = absolute + 1; //1 for the *
IRegion region = doc.getLineInformationOfOffset(absoluteCol);
endCol = absoluteCol - region.getOffset() + 1; //1 because we should return as if starting in 1 and not 0
return endCol;
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
} else if (ast instanceof Import) {
NameTok it = getNameForRepresentation((Import) ast, shortMessage, true);
endCol = it.beginColumn + shortMessage.length();
return endCol;
} else {
throw new RuntimeException("It is not an import");
}
}
//no import... make it regular
if (generator instanceof SourceToken) {
return ((SourceToken) generator).getColEnd(getOnlyToFirstDot);
}
return -1;
}
public String toString() {
return getMessage();
}
public List<String> getAdditionalInfo() {
return additionalInfo;
}
public void addAdditionalInfo(String info) {
if (this.additionalInfo == null) {
this.additionalInfo = new ArrayList<String>();
}
this.additionalInfo.add(info);
}
String message = null;
public String getMessage() {
if (message != null) {
return message;
}
String typeStr = getTypeStr();
if (typeStr == null) {
throw new AssertionError("Unable to get message for type: " + getType());
}
Object shortMessage = getShortMessage();
if (shortMessage == null) {
throw new AssertionError("Unable to get shortMessage (" + typeStr + ")");
}
if (shortMessage instanceof Object[]) {
Object[] o = (Object[]) shortMessage;
//if we have the same number of %s as objects in the array, make the format
int countPercS = StringUtils.countPercS(typeStr);
if (countPercS == o.length) {
return com.aptana.shared_core.string.StringUtils.format(typeStr, o);
} else if (countPercS == 1) {
//if we have only 1, all parameters should be concatenated in a single string
FastStringBuffer buf = new FastStringBuffer();
for (int i = 0; i < o.length; i++) {
buf.append(o[i].toString());
if (i != o.length - 1) {
buf.append(" ");
}
}
shortMessage = buf.toString();
} else {
throw new AssertionError("The number of %s is not the number of passed parameters nor 1");
}
}
message = com.aptana.shared_core.string.StringUtils.format(typeStr, shortMessage);
return message;
}
public IToken getGenerator() {
return generator;
}
}