/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.internal.element;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.Identifier;
import com.google.dart.engine.ast.visitor.NodeLocator;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ElementAnnotation;
import com.google.dart.engine.element.ElementLocation;
import com.google.dart.engine.element.ElementVisitor;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.utilities.collection.BooleanArray;
import com.google.dart.engine.utilities.general.StringUtilities;
import com.google.dart.engine.utilities.translation.DartName;
/**
* The abstract class {@code ElementImpl} implements the behavior common to objects that implement
* an {@link Element}.
*
* @coverage dart.engine.element
*/
public abstract class ElementImpl implements Element {
/**
* The enclosing element of this element, or {@code null} if this element is at the root of the
* element structure.
*/
private ElementImpl enclosingElement;
/**
* The name of this element.
*/
private final String name;
/**
* The offset of the name of this element in the file that contains the declaration of this
* element.
*/
private int nameOffset;
/**
* A bit-encoded form of the modifiers associated with this element.
*/
private int modifiers;
/**
* An array containing all of the metadata associated with this element.
*/
private ElementAnnotation[] metadata = ElementAnnotationImpl.EMPTY_ARRAY;
/**
* A cached copy of the calculated hashCode for this element.
*/
private int cachedHashCode;
/**
* Initialize a newly created element to have the given name.
*
* @param name the name of this element
*/
@DartName("forNode")
public ElementImpl(Identifier name) {
this(name == null ? "" : name.getName(), name == null ? -1 : name.getOffset());
}
/**
* Initialize a newly created element to have the given name.
*
* @param name the name of this element
* @param nameOffset the offset of the name of this element in the file that contains the
* declaration of this element
*/
public ElementImpl(String name, int nameOffset) {
this.name = StringUtilities.intern(name);
this.nameOffset = nameOffset;
}
@Override
public String computeDocumentationComment() throws AnalysisException {
AnalysisContext context = getContext();
if (context == null) {
return null;
}
return context.computeDocumentationComment(this);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || hashCode() != object.hashCode()) {
return false;
}
return object.getClass() == getClass()
&& ((Element) object).getLocation().equals(getLocation());
}
@Override
@SuppressWarnings("unchecked")
public <E extends Element> E getAncestor(Class<E> elementClass) {
Element ancestor = enclosingElement;
while (ancestor != null && !elementClass.isInstance(ancestor)) {
ancestor = ancestor.getEnclosingElement();
}
return (E) ancestor;
}
/**
* Return the child of this element that is uniquely identified by the given identifier, or
* {@code null} if there is no such child.
*
* @param identifier the identifier used to select a child
* @return the child of this element with the given identifier
*/
public ElementImpl getChild(String identifier) {
return null;
}
@Override
public AnalysisContext getContext() {
if (enclosingElement == null) {
return null;
}
return enclosingElement.getContext();
}
@Override
public String getDisplayName() {
return name;
}
@Override
public Element getEnclosingElement() {
return enclosingElement;
}
@Override
public String getExtendedDisplayName(String shortName) {
if (shortName == null) {
shortName = getDisplayName();
}
Source source = getSource();
if (source != null) {
return shortName + " (" + source.getFullName() + ")";
}
return shortName;
}
@Override
public LibraryElement getLibrary() {
return getAncestor(LibraryElement.class);
}
@Override
public ElementLocation getLocation() {
return new ElementLocationImpl(this);
}
@Override
public ElementAnnotation[] getMetadata() {
return metadata;
}
@Override
public String getName() {
return name;
}
@Override
public int getNameOffset() {
return nameOffset;
}
@Override
public AstNode getNode() throws AnalysisException {
return getNodeMatching(AstNode.class);
}
@Override
public Source getSource() {
if (enclosingElement == null) {
return null;
}
return enclosingElement.getSource();
}
@Override
public CompilationUnit getUnit() throws AnalysisException {
return getContext().resolveCompilationUnit(getSource(), getLibrary());
}
@Override
public int hashCode() {
// TODO: We might want to re-visit this optimization in the future.
// We cache the hash code value as this is a very frequently called method.
if (cachedHashCode == 0) {
int hashIdentifier = getIdentifier().hashCode();
Element enclosing = getEnclosingElement();
if (enclosing != null) {
cachedHashCode = hashIdentifier + enclosing.hashCode();
} else {
cachedHashCode = hashIdentifier;
}
}
return cachedHashCode;
}
@Override
public boolean isAccessibleIn(LibraryElement library) {
if (Identifier.isPrivateName(name)) {
return library.equals(getLibrary());
}
return true;
}
@Override
public boolean isDeprecated() {
for (ElementAnnotation annotation : metadata) {
if (annotation.isDeprecated()) {
return true;
}
}
return false;
}
@Override
public boolean isOverride() {
for (ElementAnnotation annotation : metadata) {
if (annotation.isOverride()) {
return true;
}
}
return false;
}
@Override
public boolean isPrivate() {
String name = getDisplayName();
if (name == null) {
return true;
}
return Identifier.isPrivateName(name);
}
@Override
public boolean isPublic() {
return !isPrivate();
}
@Override
public boolean isSynthetic() {
return hasModifier(Modifier.SYNTHETIC);
}
/**
* Set the enclosing element of this element to the given element.
*
* @param element the enclosing element of this element
*/
public void setEnclosingElement(Element element) {
enclosingElement = (ElementImpl) element;
}
/**
* Set the metadata associate with this element to the given array of annotations.
*
* @param metadata the metadata to be associated with this element
*/
public void setMetadata(ElementAnnotation[] metadata) {
this.metadata = metadata;
}
/**
* Set the offset of the name of this element in the file that contains the declaration of this
* element to the given value. This is normally done via the constructor, but this method is
* provided to support unnamed constructors.
*
* @param nameOffset the offset to the beginning of the name
*/
public void setNameOffset(int nameOffset) {
this.nameOffset = nameOffset;
}
/**
* Set whether this element is synthetic to correspond to the given value.
*
* @param isSynthetic {@code true} if the element is synthetic
*/
public void setSynthetic(boolean isSynthetic) {
setModifier(Modifier.SYNTHETIC, isSynthetic);
}
@Override
public final String toString() {
StringBuilder builder = new StringBuilder();
appendTo(builder);
return builder.toString();
}
@Override
public void visitChildren(ElementVisitor<?> visitor) {
// There are no children to visit
}
/**
* Append a textual representation of this type to the given builder.
*
* @param builder the builder to which the text is to be appended
*/
protected void appendTo(StringBuilder builder) {
if (name == null) {
builder.append("<unnamed ");
builder.append(getClass().getName());
builder.append(">");
} else {
builder.append(name);
}
}
/**
* Set this {@link Element} as an enclosing for given.
*
* @param element the element to enclose, must be {@link ElementImpl}
*/
protected void encloseElement(ElementImpl element) {
element.setEnclosingElement(this);
}
/**
* Return an identifier that uniquely identifies this element among the children of this element's
* parent.
*
* @return an identifier that uniquely identifies this element relative to its parent
*/
protected String getIdentifier() {
return getName();
}
/**
* Return the resolved {@link AstNode} of the given type enclosing {@link #getNameOffset()}.
*/
protected <T extends AstNode> T getNodeMatching(Class<T> clazz) throws AnalysisException {
CompilationUnit unit = getUnit();
if (unit == null) {
return null;
}
int offset = getNameOffset();
AstNode node = new NodeLocator(offset).searchWithin(unit);
if (node == null) {
return null;
}
return node.getAncestor(clazz);
}
/**
* Return {@code true} if this element has the given modifier associated with it.
*
* @param modifier the modifier being tested for
* @return {@code true} if this element has the given modifier associated with it
*/
protected boolean hasModifier(Modifier modifier) {
return BooleanArray.getEnum(modifiers, modifier);
}
/**
* If the given child is not {@code null}, use the given visitor to visit it.
*
* @param child the child to be visited
* @param visitor the visitor to be used to visit the child
*/
protected void safelyVisitChild(Element child, ElementVisitor<?> visitor) {
if (child != null) {
child.accept(visitor);
}
}
/**
* Use the given visitor to visit all of the children in the given array.
*
* @param children the children to be visited
* @param visitor the visitor being used to visit the children
*/
protected void safelyVisitChildren(Element[] children, ElementVisitor<?> visitor) {
if (children != null) {
for (Element child : children) {
child.accept(visitor);
}
}
}
/**
* Set whether the given modifier is associated with this element to correspond to the given
* value.
*
* @param modifier the modifier to be set
* @param value {@code true} if the modifier is to be associated with this element
*/
protected void setModifier(Modifier modifier, boolean value) {
modifiers = BooleanArray.setEnum(modifiers, modifier, value);
}
}