/**
* Copyright 2009, Google Inc. All rights reserved.
* Licensed to PSF under a Contributor Agreement.
*/
package org.python.indexer;
import org.python.indexer.ast.NAttribute;
import org.python.indexer.ast.NName;
import org.python.indexer.ast.NNode;
import org.python.indexer.ast.NStr;
/**
* 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;
private String file;
private String name;
private int flags;
public Ref(NNode node) {
if (node == null) {
throw new IllegalArgumentException("null node");
}
file = node.getFile();
start = node.start();
if (node instanceof NName) {
NName nn = ((NName)node);
name = nn.id;
if (nn.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 NStr) {
markAsString();
name = ((NStr)node).n.toString();
} else {
throw new IllegalArgumentException("I don't know what " + node + " is.");
}
NNode parent = node.getParent();
if ((parent instanceof NAttribute)
&& node == ((NAttribute)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(String path, int offset, 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.
*/
public String getFile() {
return file;
}
/**
* Returns the text of the reference.
*/
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());
}
@Override
public String toString() {
return "<Ref:" + file + ":" + name + ":" + start + ">";
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Ref)) {
return false;
}
Ref ref = (Ref)obj;
if (start != ref.start) {
return false;
}
if (name != null) {
if (!name.equals(ref.name)) {
return false;
}
} else {
if (ref.name != null) {
return false;
}
}
if (file != null) {
if (!file.equals(ref.file)) {
return false;
}
} else {
if (ref.file != null) {
return false;
}
}
// This should never happen, but checking it here can help surface bugs.
if (flags != ref.flags) {
return false;
}
return true;
}
@Override
public int hashCode() {
return ("" + file + name + start).hashCode();
}
}