package org.eclipse.jdt.internal.core; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import org.eclipse.core.resources.*; | |
import org.eclipse.core.runtime.*; | |
import org.eclipse.jdt.core.*; | |
import org.eclipse.jdt.core.jdom.*; | |
import java.util.ArrayList; | |
/** | |
* Root of Java element handle hierarchy. | |
* | |
* @see IJavaElement | |
*/ | |
public abstract class JavaElement extends PlatformObject implements IJavaElement { | |
public static final char JEM_JAVAPROJECT= '='; | |
public static final char JEM_PACKAGEFRAGMENTROOT= Path.SEPARATOR; | |
public static final char JEM_PACKAGEFRAGMENT= '<'; | |
public static final char JEM_FIELD= '^'; | |
public static final char JEM_METHOD= '~'; | |
public static final char JEM_INITIALIZER= '|'; | |
public static final char JEM_COMPILATIONUNIT= '{'; | |
public static final char JEM_CLASSFILE= '('; | |
public static final char JEM_TYPE= '['; | |
public static final char JEM_PACKAGEDECLARATION= '%'; | |
public static final char JEM_IMPORTDECLARATION= '#'; | |
/** | |
* A count to uniquely identify this element in the case | |
* that a duplicate named element exists. For example, if | |
* there are two fields in a compilation unit with the | |
* same name, the occurrence count is used to distinguish | |
* them. The occurrence count starts at 1 (i.e. the first | |
* occurrence is occurrence 1, not occurrence 0). | |
*/ | |
protected int fOccurrenceCount = 1; | |
/** | |
* This element's type - one of the constants defined | |
* in IJavaLanguageElementTypes. | |
*/ | |
protected int fLEType = 0; | |
/** | |
* This element's parent, or <code>null</code> if this | |
* element does not have a parent. | |
*/ | |
protected IJavaElement fParent; | |
/** | |
* This element's name, or an empty <code>String</code> if this | |
* element does not have a name. | |
*/ | |
protected String fName; | |
/** | |
* Direct access to the Java Model Manager | |
*/ | |
protected static JavaModelManager fgJavaModelManager = JavaModelManager.getJavaModelManager(); | |
protected static final Object NO_INFO = new Object(); | |
/** | |
* Constructs a handle for a java element of the specified type, with | |
* the given parent element and name. | |
* | |
* @param type - one of the constants defined in IJavaLanguageElement | |
* | |
* @exception IllegalArgumentException if the type is not one of the valid | |
* Java element type constants | |
* | |
*/ | |
protected JavaElement(int type, IJavaElement parent, String name) throws IllegalArgumentException { | |
if (type < JAVA_MODEL || type > IMPORT_DECLARATION) { | |
throw new IllegalArgumentException(Util.bind("element.invalidType")); //$NON-NLS-1$ | |
} | |
fLEType= type; | |
fParent= parent; | |
fName= name; | |
} | |
/** | |
* @see IOpenable | |
*/ | |
public void close() throws JavaModelException { | |
Object info = fgJavaModelManager.peekAtInfo(this); | |
if (info != null) { | |
if (JavaModelManager.VERBOSE && this instanceof JavaModel) { | |
System.out.println("CLOSING Java Model"); //$NON-NLS-1$ | |
// done only when exiting the workbench: disable verbose | |
JavaModelManager.VERBOSE = false; | |
} | |
if (this instanceof IParent) { | |
IJavaElement[] children = ((JavaElementInfo) info).getChildren(); | |
for (int i = 0, size = children.length; i < size; ++i) { | |
JavaElement child = (JavaElement) children[i]; | |
child.close(); | |
} | |
} | |
closing(info); | |
fgJavaModelManager.removeInfo(this); | |
if (JavaModelManager.VERBOSE){ | |
System.out.println("-> Package cache size = " + fgJavaModelManager.cache.pkgSize()); //$NON-NLS-1$ | |
System.out.println("-> Openable cache filling rate = " + fgJavaModelManager.cache.openableFillingRate() + "%"); //$NON-NLS-1$//$NON-NLS-2$ | |
} | |
} | |
} | |
/** | |
* This element is being closed. Do any necessary cleanup. | |
*/ | |
protected void closing(Object info) throws JavaModelException { | |
if (JavaModelManager.VERBOSE){ | |
System.out.println("CLOSING Element ("+ Thread.currentThread()+"): " + this.toStringWithAncestors()); //$NON-NLS-1$//$NON-NLS-2$ | |
} | |
} | |
/** | |
* Returns true if this handle represents the same Java element | |
* as the given handle. By default, two handles represent the same | |
* element if they are identical or if they represent the same type | |
* of element, have equal names, parents, and occurrence counts. | |
* | |
* <p>If a subclass has other requirements for equality, this method | |
* must be overridden. | |
* | |
* @see Object#equals | |
*/ | |
public boolean equals(Object o) { | |
if (fParent == null) return super.equals(o); | |
if (this == o) | |
return true; | |
if (o instanceof JavaElement) { | |
JavaElement other = (JavaElement) o; | |
if (fLEType != other.fLEType) | |
return false; | |
return fName.equals(other.fName) && | |
fParent.equals(other.fParent) && | |
fOccurrenceCount == other.fOccurrenceCount; | |
} | |
return false; | |
} | |
/** | |
* Returns true if this <code>JavaElement</code> is equivalent to the given | |
* <code>IDOMNode</code>. | |
*/ | |
protected boolean equalsDOMNode(IDOMNode node) throws JavaModelException { | |
return false; | |
} | |
/** | |
* @see IJavaElement | |
*/ | |
public boolean exists() { | |
try { | |
getRawInfo(); | |
return true; | |
} catch (JavaModelException e) { | |
} | |
return false; | |
} | |
/** | |
* Returns the <code>IDOMNode</code> that corresponds to this <code>JavaElement</code> | |
* or <code>null</code> if there is no corresponding node. | |
*/ | |
public IDOMNode findNode(IDOMCompilationUnit dom) { | |
int type = getElementType(); | |
if (type == IJavaElement.COMPILATION_UNIT || | |
type == IJavaElement.FIELD || | |
type == IJavaElement.IMPORT_DECLARATION || | |
type == IJavaElement.INITIALIZER || | |
type == IJavaElement.METHOD || | |
type == IJavaElement.PACKAGE_DECLARATION || | |
type == IJavaElement.TYPE) { | |
ArrayList path = new ArrayList(); | |
IJavaElement element = this; | |
while (element != null && element.getElementType() != IJavaElement.COMPILATION_UNIT) { | |
if (element.getElementType() != IJavaElement.IMPORT_CONTAINER) { | |
// the DOM does not have import containers, so skip them | |
path.add(0, element); | |
} | |
element = element.getParent(); | |
} | |
if (path.size() == 0) { | |
try { | |
if (equalsDOMNode(dom)) { | |
return dom; | |
} else { | |
return null; | |
} | |
} catch(JavaModelException e) { | |
return null; | |
} | |
} | |
return ((JavaElement) path.get(0)).followPath(path, 0, dom.getFirstChild()); | |
} else { | |
return null; | |
} | |
} | |
/** | |
*/ | |
protected IDOMNode followPath(ArrayList path, int position, IDOMNode node) { | |
try { | |
if (equalsDOMNode(node)) { | |
if (position == (path.size() - 1)) { | |
return node; | |
} else { | |
if (node.getFirstChild() != null) { | |
position++; | |
return ((JavaElement)path.get(position)).followPath(path, position, node.getFirstChild()); | |
} else { | |
return null; | |
} | |
} | |
} else if (node.getNextNode() != null) { | |
return followPath(path, position, node.getNextNode()); | |
} else { | |
return null; | |
} | |
} catch (JavaModelException e) { | |
return null; | |
} | |
} | |
/** | |
* @see IJavaElement | |
*/ | |
public IJavaElement getAncestor(int ancestorType) { | |
IJavaElement element = this; | |
while (element != null && element.getElementType() != ancestorType) { | |
element= element.getParent(); | |
} | |
return element; | |
} | |
/** | |
* @see IParent | |
*/ | |
public IJavaElement[] getChildren() throws JavaModelException { | |
return getElementInfo().getChildren(); | |
} | |
/** | |
* Returns a collection of (immediate) children of this node of the | |
* specified type. | |
* | |
* @param type - one of constants defined by IJavaLanguageElementTypes | |
*/ | |
public ArrayList getChildrenOfType(int type) throws JavaModelException { | |
IJavaElement[] children = getChildren(); | |
int size = children.length; | |
ArrayList list = new ArrayList(size); | |
for (int i = 0; i < size; ++i) { | |
JavaElement elt = (JavaElement)children[i]; | |
if (elt.getElementType() == type) { | |
list.add(elt); | |
} | |
} | |
return list; | |
} | |
/** | |
* @see IMember | |
*/ | |
public IClassFile getClassFile() { | |
return null; | |
} | |
/** | |
* @see IMember | |
*/ | |
public ICompilationUnit getCompilationUnit() { | |
return null; | |
} | |
/** | |
* Returns the info for this handle. | |
* If this element is not already open, it and all of its parents are opened. | |
* Does not return null. | |
* | |
* @exception JavaModelException if the element is not present or not accessible | |
*/ | |
public JavaElementInfo getElementInfo() throws JavaModelException { | |
synchronized(fgJavaModelManager){ | |
Object info = fgJavaModelManager.getInfo(this); | |
if (info == null) { | |
openHierarchy(); | |
info= fgJavaModelManager.getInfo(this); | |
if (info == null) { | |
throw newNotPresentException(); | |
} | |
} | |
return (JavaElementInfo)info; | |
} | |
} | |
/** | |
* @see IAdaptable | |
*/ | |
public String getElementName() { | |
return fName; | |
} | |
/** | |
* @see IJavaElement | |
*/ | |
public int getElementType() { | |
return fLEType; | |
} | |
/** | |
* @see IJavaElement | |
*/ | |
public String getHandleIdentifier() { | |
return getHandleMemento(); | |
} | |
/** | |
* @see JavaElement#getHandleMemento() | |
*/ | |
public String getHandleMemento(){ | |
StringBuffer buff= new StringBuffer(((JavaElement)getParent()).getHandleMemento()); | |
buff.append(getHandleMementoDelimiter()); | |
buff.append(getElementName()); | |
return buff.toString(); | |
} | |
/** | |
* Returns the <code>char</code> that marks the start of this handles | |
* contribution to a memento. | |
*/ | |
protected abstract char getHandleMementoDelimiter(); | |
/** | |
* @see IJavaElement | |
*/ | |
public IJavaModel getJavaModel() { | |
return getParent().getJavaModel(); | |
} | |
/** | |
* Returns the JavaModelManager | |
*/ | |
public JavaModelManager getJavaModelManager() { | |
return fgJavaModelManager; | |
} | |
/** | |
* @see IJavaElement | |
*/ | |
public IJavaProject getJavaProject() { | |
return getParent().getJavaProject(); | |
} | |
/** | |
* Returns the occurrence count of the handle. | |
*/ | |
protected int getOccurrenceCount() { | |
return fOccurrenceCount; | |
} | |
/* | |
* @see IJavaElement | |
*/ | |
public IOpenable getOpenable() { | |
return this.getOpenableParent(); | |
} | |
/** | |
* Return the first instance of IOpenable in the parent | |
* hierarchy of this element. | |
* | |
* <p>Subclasses that are not IOpenable's must override this method. | |
*/ | |
public IOpenable getOpenableParent() { | |
return (IOpenable)fParent; | |
} | |
/** | |
* @see IJavaElement | |
*/ | |
public IJavaElement getParent() { | |
return fParent; | |
} | |
/** | |
* Returns the info for this handle. | |
* If this element is not already open, it and all of its parents are opened. | |
* Does not return null. | |
* | |
* @exception JavaModelException if the element is not present or not accessible | |
*/ | |
public Object getRawInfo() throws JavaModelException { | |
Object info = fgJavaModelManager.getInfo(this); | |
if (info == null) { | |
openHierarchy(); | |
info= fgJavaModelManager.getInfo(this); | |
if (info == null) { | |
throw newNotPresentException(); | |
} | |
} | |
return info; | |
} | |
/** | |
* Returns the element that is located at the given source position | |
* in this element. This is a helper method for <code>ICompilationUnit#getElementAt</code>, | |
* and only works on compilation units and types. The position given is | |
* known to be within this element's source range already, and if no finer | |
* grained element is found at the position, this element is returned. | |
*/ | |
protected IJavaElement getSourceElementAt(int position) throws JavaModelException { | |
if (this instanceof ISourceReference) { | |
IJavaElement[] children = getChildren(); | |
int i; | |
for (i = 0; i < children.length; i++) { | |
IJavaElement aChild = children[i]; | |
if (aChild instanceof SourceRefElement) { | |
SourceRefElement child = (SourceRefElement) children[i]; | |
ISourceRange range = child.getSourceRange(); | |
if (position < range.getOffset() + range.getLength() && position >= range.getOffset()) { | |
if (child instanceof IParent) { | |
return child.getSourceElementAt(position); | |
} else { | |
return child; | |
} | |
} | |
} | |
} | |
} else { | |
// should not happen | |
Assert.isTrue(false); | |
} | |
return this; | |
} | |
/** | |
* Returns the SourceMapper facility for this element, or | |
* <code>null</code> if this element does not have a | |
* SourceMapper. | |
*/ | |
public SourceMapper getSourceMapper() { | |
return ((JavaElement)getParent()).getSourceMapper(); | |
} | |
public abstract IResource getUnderlyingResource() throws JavaModelException; | |
/** | |
* Returns the workspace associated with this object. | |
*/ | |
public IWorkspace getWorkspace() { | |
return getJavaModel().getWorkspace(); | |
} | |
/** | |
* Returns the hash code for this Java element. By default, | |
* the hash code for an element is a combination of its name | |
* and parent's hash code. Elements with other requirements must | |
* override this method. | |
*/ | |
public int hashCode() { | |
if (fParent == null) return super.hashCode(); | |
return Util.combineHashCodes(fName.hashCode(), fParent.hashCode()); | |
} | |
/** | |
* Returns true if this element is an ancestor of the given element, | |
* otherwise false. | |
*/ | |
protected boolean isAncestorOf(IJavaElement e) { | |
IJavaElement parent= e.getParent(); | |
while (parent != null && !parent.equals(this)) { | |
parent= parent.getParent(); | |
} | |
return parent != null; | |
} | |
/** | |
* @see IJavaElement | |
*/ | |
public boolean isReadOnly() { | |
return false; | |
} | |
/** | |
* @see IJavaElement | |
*/ | |
public boolean isStructureKnown() throws JavaModelException { | |
return getElementInfo().isStructureKnown(); | |
} | |
/** | |
* Creates and returns and not present exception for this element. | |
*/ | |
protected JavaModelException newNotPresentException() { | |
return new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST, this)); | |
} | |
/** | |
* Default is to not do any source indices updates. | |
*/ | |
public void offsetSourceEndAndChildren(int amount, IJavaElement child) { | |
} | |
/** | |
* Default behaviour is not to change the source range | |
* for the Java element | |
*/ | |
public void offsetSourceRange(int amount) { | |
} | |
/** | |
* Opens this element and all parents that are not already open. | |
* | |
* @exception JavaModelException this element is not present or accessible | |
*/ | |
protected void openHierarchy() throws JavaModelException { | |
if (this instanceof IOpenable) { | |
((Openable) this).openWhenClosed(null); | |
} else { | |
Openable openableParent = (Openable)getOpenableParent(); | |
if (openableParent != null) { | |
JavaElementInfo openableParentInfo = (JavaElementInfo) fgJavaModelManager.getInfo((IJavaElement) openableParent); | |
if (openableParentInfo == null) { | |
openableParent.openWhenClosed(null); | |
} else { | |
throw newNotPresentException(); | |
} | |
} | |
} | |
} | |
/** | |
* This element has just been opened. Do any necessary setup. | |
*/ | |
protected void opening(Object info) { | |
} | |
/** | |
*/ | |
public String readableName() { | |
return this.getElementName(); | |
} | |
/** | |
* Removes all cached info from the Java Model, including all children, | |
* but does not close this element. | |
*/ | |
protected void removeInfo() { | |
Object info = fgJavaModelManager.peekAtInfo(this); | |
if (info != null) { | |
if (this instanceof IParent) { | |
IJavaElement[] children = ((JavaElementInfo)info).getChildren(); | |
for (int i = 0, size = children.length; i < size; ++i) { | |
JavaElement child = (JavaElement) children[i]; | |
child.removeInfo(); | |
} | |
} | |
fgJavaModelManager.removeInfo(this); | |
} | |
} | |
/** | |
* Returns a copy of this element rooted at the given project. | |
*/ | |
public abstract IJavaElement rootedAt(IJavaProject project); | |
/** | |
* Runs a Java Model Operation | |
*/ | |
protected void runOperation(JavaModelOperation operation, IProgressMonitor monitor) throws JavaModelException { | |
fgJavaModelManager.runOperation(operation, monitor); | |
} | |
/** | |
* Sets the occurrence count of the handle. | |
*/ | |
protected void setOccurrenceCount(int count) { | |
fOccurrenceCount = count; | |
} | |
protected String tabString(int tab) { | |
StringBuffer buffer = new StringBuffer(); | |
for (int i = tab; i > 0; i--) | |
buffer.append(" "); //$NON-NLS-1$ | |
return buffer.toString(); | |
} | |
/** | |
* Debugging purposes | |
*/ | |
public String toDebugString() { | |
StringBuffer buffer = new StringBuffer(); | |
this.toStringInfo(0, buffer, NO_INFO); | |
return buffer.toString(); | |
} | |
/** | |
* Debugging purposes | |
*/ | |
public String toString() { | |
StringBuffer buffer = new StringBuffer(); | |
toString(0, buffer); | |
return buffer.toString(); | |
} | |
/** | |
* Debugging purposes | |
*/ | |
protected void toString(int tab, StringBuffer buffer) { | |
Object info = this.toStringInfo(tab, buffer); | |
if (tab == 0) { | |
this.toStringAncestors(buffer); | |
} | |
this.toStringChildren(tab, buffer, info); | |
} | |
/** | |
* Debugging purposes | |
*/ | |
public String toStringWithAncestors() { | |
StringBuffer buffer = new StringBuffer(); | |
this.toStringInfo(0, buffer, NO_INFO); | |
this.toStringAncestors(buffer); | |
return buffer.toString(); | |
} | |
/** | |
* Debugging purposes | |
*/ | |
protected void toStringAncestors(StringBuffer buffer) { | |
JavaElement parent = (JavaElement)this.getParent(); | |
if (parent != null && parent.getParent() != null) { | |
buffer.append(" [in "); //$NON-NLS-1$ | |
parent.toStringInfo(0, buffer, NO_INFO); | |
parent.toStringAncestors(buffer); | |
buffer.append("]"); //$NON-NLS-1$ | |
} | |
} | |
/** | |
* Debugging purposes | |
*/ | |
protected void toStringChildren(int tab, StringBuffer buffer, Object info) { | |
if (info == null || !(info instanceof JavaElementInfo)) return; | |
IJavaElement[] children = ((JavaElementInfo)info).getChildren(); | |
for (int i = 0; i < children.length; i++) { | |
buffer.append("\n"); //$NON-NLS-1$ | |
((JavaElement)children[i]).toString(tab + 1, buffer); | |
} | |
} | |
/** | |
* Debugging purposes | |
*/ | |
public Object toStringInfo(int tab, StringBuffer buffer) { | |
Object info = fgJavaModelManager.peekAtInfo(this); | |
this.toStringInfo(tab, buffer, info); | |
return info; | |
} | |
/** | |
* Debugging purposes | |
*/ | |
protected void toStringInfo(int tab, StringBuffer buffer, Object info) { | |
buffer.append(this.tabString(tab)); | |
buffer.append(getElementName()); | |
if (info == null) { | |
buffer.append(" (not open)"); //$NON-NLS-1$ | |
} | |
} | |
/** | |
* Updates the source end position for this element. | |
* Default behaviour is to do nothing. | |
*/ | |
public void triggerSourceEndOffset(int amount, int nameStart, int nameEnd) { | |
} | |
/** | |
* Updates the source positions for this element. | |
* Default behaviour is to do nothing. | |
*/ | |
public void triggerSourceRangeOffset(int amount, int nameStart, int nameEnd) { | |
} | |
} |