package org.yinwang.pysonar;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yinwang.pysonar.ast.Attribute;
import org.yinwang.pysonar.ast.Name;
import org.yinwang.pysonar.ast.Node;
import org.yinwang.pysonar.ast.Str;
/**
* Encapsulates information about a binding reference.
*/
public class Ref {
private static final int ATTRIBUTE = 0x1;
private static final int CALL = 0x2; // function/method call
private static final int NEW = 0x4; // instantiation
private static final int STRING = 0x8; // source node is a String
private int start;
@Nullable
private String file;
@Nullable
private String name;
private int flags;
public Ref(@NotNull Node node) {
file = node.getFile();
start = node.start;
if (node instanceof Name) {
Name n = ((Name)node);
name = n.getId();
if (n.isCall()) {
// We don't always have enough information at this point to know
// if it's a constructor call or a regular function/method call,
// so we just determine if it looks like a call or not, and the
// indexer will convert constructor-calls to NEW in a later pass.
markAsCall();
}
} else if (node instanceof Str) {
markAsString();
name = ((Str)node).getStr();
} else {
throw new IllegalArgumentException("I don't know what " + node + " is.");
}
Node parent = node.getParent();
if ((parent instanceof Attribute)
&& node == ((Attribute)parent).attr) {
markAsAttribute();
}
}
/**
* Constructor that provides a way for clients to add additional references
* not associated with an AST node (e.g. inside a comment or doc string).
* @param path absolute path to the file containing the reference
* @param offset the 0-indexed file offset of the reference
* @param text the text of the reference
*/
public Ref(@Nullable String path, int offset, @Nullable String text) {
if (path == null) {
throw new IllegalArgumentException("'path' cannot be null");
}
if (text == null) {
throw new IllegalArgumentException("'text' cannot be null");
}
file = path;
start = offset;
name = text;
}
/**
* Returns the file containing the reference.
*/
@Nullable
public String getFile() {
return file;
}
/**
* Returns the text of the reference.
*/
@Nullable
public String getName() {
return name;
}
/**
* Returns the starting file offset of the reference.
*/
public int start() {
return start;
}
/**
* Returns the ending file offset of the reference.
*/
public int end() {
return start + length();
}
/**
* Returns the length of the reference text.
*/
public int length() {
return isString() ? name.length() + 2 : name.length();
}
/**
* Returns {@code true} if this reference was unquoted name.
*/
public boolean isName() {
return !isString();
}
/**
* Returns {@code true} if this reference was an attribute
* of some other node.
*/
public boolean isAttribute() {
return (flags & ATTRIBUTE) != 0;
}
public void markAsAttribute() {
flags |= ATTRIBUTE;
}
/**
* Returns {@code true} if this reference was a quoted name.
* If so, the {@link #start} and {@link #length} include the positions
* of the opening and closing quotes, but {@link #isName} returns the
* text within the quotes.
*/
public boolean isString() {
return (flags & STRING) != 0;
}
public void markAsString() {
flags |= STRING;
}
/**
* Returns {@code true} if this reference is a function or method call.
*/
public boolean isCall() {
return (flags & CALL) != 0;
}
/**
* Returns {@code true} if this reference is a class instantiation.
*/
public void markAsCall() {
flags |= CALL;
flags &= ~NEW;
}
public boolean isNew() {
return (flags & NEW) != 0;
}
public void markAsNew() {
flags |= NEW;
flags &= ~CALL;
}
public boolean isRef() {
return !(isCall() || isNew());
}
@NotNull
@Override
public String toString() {
return "<Ref:" + file + ":" + name + ":" + start + ">";
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Ref)) {
return false;
} else {
Ref ref = (Ref) obj;
return (start == ref.start &&
(file == null && ref.file == null) ||
(file != null && ref.file != null && file.equals(ref.file)));
}
}
@Override
public int hashCode() {
return ("" + file + start).hashCode();
}
}