blob: cc6944b4754388c37cafc4c0cb6cf78933a5d8a0 [file] [log] [blame]
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.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.internal.codeassist.CompletionEngine;
import org.eclipse.jdt.internal.codeassist.ICompletionRequestor;
import org.eclipse.jdt.internal.codeassist.ISelectionRequestor;
import org.eclipse.jdt.internal.codeassist.ISearchableNameEnvironment;
import org.eclipse.jdt.internal.codeassist.SelectionEngine;
import org.eclipse.jdt.internal.compiler.ConfigurableOption;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.internal.core.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;
/**
* Abstract class for implementations of java elements which are IOpenable.
*
* @see IJavaElement, IOpenable
*/
public abstract class Openable extends JavaElement implements IOpenable, IBufferChangedListener {
protected Openable(int type, IJavaElement parent, String name) {
super(type, parent, name);
}
/**
* The buffer associated with this element has changed. Registers
* this element as being out of synch with its buffer's contents.
* If the buffer has been closed, this element is set as NOT out of
* synch with the contents.
*
* @see IBufferChangedListener
*/
public void bufferChanged(BufferChangedEvent event) {
if (event.getBuffer().isClosed()) {
fgJavaModelManager.getElementsOutOfSynchWithBuffers().remove(this);
} else {
fgJavaModelManager.getElementsOutOfSynchWithBuffers().put(this, this);
}
}
/**
* Updates the info objects for this element and all of its children by
* removing the current infos, generating new infos, and then placing
* the new infos into the Java Model cache tables.
*/
protected void buildStructure(OpenableElementInfo info, IProgressMonitor pm) throws JavaModelException {
// remove existing (old) infos
removeInfo();
Hashtable newElements = new Hashtable(11);
info.setIsStructureKnown(generateInfos(info, pm, newElements, getUnderlyingResource()));
fgJavaModelManager.getElementsOutOfSynchWithBuffers().remove(this);
for (Enumeration e = newElements.keys(); e.hasMoreElements();) {
IJavaElement key = (IJavaElement) e.nextElement();
Object value = newElements.get(key);
fgJavaModelManager.putInfo(key, value);
}
// add the info for this at the end, to ensure that a getInfo cannot reply null in case the LRU cache needs
// to be flushed. Might lead to performance issues.
// see PR 1G2K5S7: ITPJCORE:ALL - NPE when accessing source for a binary type
fgJavaModelManager.putInfo(this, info);
}
/**
* Close the buffer associated with this element, if any.
*/
protected void closeBuffer(OpenableElementInfo info) {
IBuffer buffer = null;
buffer = getBufferManager().getBuffer(this);
if (buffer != null) {
buffer.removeBufferChangedListener(this);
buffer.close();
fgJavaModelManager.getElementsOutOfSynchWithBuffers().remove(this);
}
}
/**
* This element is being closed. Do any necessary cleanup.
*/
protected void closing(Object info) throws JavaModelException {
OpenableElementInfo openableInfo = (OpenableElementInfo) info;
closeBuffer(openableInfo);
super.closing(info);
}
/**
* @see ICodeAssist
*/
protected void codeComplete(org.eclipse.jdt.internal.compiler.env.ICompilationUnit cu, org.eclipse.jdt.internal.compiler.env.ICompilationUnit unitToSkip, int position, ICodeCompletionRequestor requestor) throws JavaModelException {
if (requestor == null) {
throw new IllegalArgumentException(Util.bind("codeAssist.nullRequestor"/*nonNLS*/));
}
if (position < -1 || position > getBuffer().getLength()) {
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS));
}
SearchableEnvironment environment = (SearchableEnvironment) ((JavaProject) getJavaProject()).getSearchableNameEnvironment();
environment.unitToSkip = unitToSkip;
CompletionEngine engine = new CompletionEngine(environment, new CompletionRequestorWrapper(requestor), JavaModelManager.convertConfigurableOptions(JavaCore.getOptions()));
engine.complete(cu, position);
environment.unitToSkip = null;
}
/**
* @see ICodeAssist
*/
protected IJavaElement[] codeSelect(org.eclipse.jdt.internal.compiler.env.ICompilationUnit cu, int offset, int length) throws JavaModelException {
SelectionRequestor requestor= new SelectionRequestor(((JavaProject)getJavaProject()).getNameLookup(), this);
this.codeSelect(cu, offset, length, requestor);
return requestor.getElements();
}
/**
* @see ICodeAssist
*/
protected void codeSelect(org.eclipse.jdt.internal.compiler.env.ICompilationUnit cu, int offset, int length, ISelectionRequestor requestor) throws JavaModelException {
// fix for 1FWNMHH, part (1)
if (length == 0) {
return;
}
int end= getBuffer().getLength();
if (offset < 0 || length < 0 || offset + length > end ) {
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS));
}
// fix for 1FVGGKF
ISearchableNameEnvironment environment = ((JavaProject)getJavaProject()).getSearchableNameEnvironment();
// fix for 1FVXGDK
SelectionEngine engine = new SelectionEngine(environment, requestor, JavaModelManager.convertConfigurableOptions(JavaCore.getOptions()));
engine.select(cu, offset, offset + length - 1);
}
/**
* Returns a new element info for this element.
*/
protected OpenableElementInfo createElementInfo() {
return new OpenableElementInfo();
}
/**
* Builds this element's structure and properties in the given
* info object, based on this element's current contents (i.e. buffer
* contents if this element has an open buffer, or resource contents
* if this element does not have an open buffer). Children
* are placed in the given newElements table (note, this element
* has already been placed in the newElements table). Returns true
* if successful, or false if an error is encountered while determining
* the structure of this element.
*/
protected abstract boolean generateInfos(OpenableElementInfo info, IProgressMonitor pm, Hashtable newElements, IResource underlyingResource) throws JavaModelException;
/**
* Note: a buffer with no unsaved changes can be closed by the Java Model
* since it has a finite number of buffers allowed open at one time. If this
* is the first time a request is being made for the buffer, an attempt is
* made to create and fill this element's buffer. If the buffer has been
* closed since it was first opened, the buffer is re-created.
*
* @see IOpenable
*/
public IBuffer getBuffer() throws JavaModelException {
if (hasBuffer()) {
// ensure element is open
if (!isOpen()) {
getElementInfo();
}
IBuffer buffer = getBufferManager().getBuffer(this);
if (buffer == null) {
// try to (re)open a buffer
buffer = openBuffer(null);
}
return buffer;
} else {
return null;
}
}
/**
* Returns the buffer manager for this element.
*/
protected IBufferManager getBufferManager() {
return BufferManager.getDefaultBufferManager();
}
/**
* Return my underlying resource. Elements that may not have a
* corresponding resource must override this method.
*
* @see IJavaElement
*/
public IResource getCorrespondingResource() throws JavaModelException {
return getUnderlyingResource();
}
/**
* @see IJavaElement
*/
public IResource getUnderlyingResource() throws JavaModelException {
IResource parentResource = fParent.getUnderlyingResource();
if (parentResource == null) {
return null;
}
int type = parentResource.getType();
if (type == IResource.FOLDER || type == IResource.PROJECT) {
IContainer folder = (IContainer) parentResource;
IResource resource = folder.findMember(fName);
if (resource == null) {
throw newNotPresentException();
} else {
return resource;
}
} else {
return parentResource;
}
}
/**
* Returns true if this element may have an associated source buffer,
* otherwise false. Subclasses must override as required.
*/
protected boolean hasBuffer() {
return false;
}
/**
* @see IParent
*/
public boolean hasChildren() throws JavaModelException {
return getChildren().length > 0;
}
/**
* @see IOpenable
*/
public boolean hasUnsavedChanges() throws JavaModelException{
if (isReadOnly() || !isOpen()) {
return false;
}
if (getBuffer() != null && getBuffer().hasUnsavedChanges()) {
return true;
}
// for package fragments, package fragment roots, and projects must check open buffers
// to see if they have an child with unsaved changes
if (fLEType == PACKAGE_FRAGMENT ||
fLEType == PACKAGE_FRAGMENT_ROOT ||
fLEType == JAVA_PROJECT ||
fLEType == JAVA_MODEL) { // fix for 1FWNMHH, part (2)
Enumeration openBuffers= getBufferManager().getOpenBuffers();
while (openBuffers.hasMoreElements()) {
IBuffer buffer= (IBuffer)openBuffers.nextElement();
if (buffer.hasUnsavedChanges()) {
IJavaElement owner= (IJavaElement)buffer.getOwner();
if (isAncestorOf(owner)) {
return true;
}
}
}
}
return false;
}
/**
* Subclasses must override as required.
*
* @see IOpenable
*/
public boolean isConsistent() throws JavaModelException {
return true;
}
/**
*
* @see IOpenable
*/
public boolean isOpen() {
return fgJavaModelManager.getInfo(this) != null;
}
/**
* Returns true if this represents a source element.
* Openable source elements have an associated buffer created
* when they are opened.
*/
protected boolean isSourceElement() {
return false;
}
/**
* @see IOpenable
*/
public void makeConsistent(IProgressMonitor pm) throws JavaModelException {
if (!isConsistent()) {
buildStructure((OpenableElementInfo)getElementInfo(), pm);
}
}
/**
* @see IOpenable
*/
public void open(IProgressMonitor pm) throws JavaModelException {
if (!isOpen()) {
openWhenClosed(pm);
}
}
/**
* Opens a buffer on the contents of this element, and returns
* the buffer, or returns <code>null</code> if opening fails.
* By default, do nothing - subclasses that have buffers
* must override as required.
*/
protected IBuffer openBuffer(IProgressMonitor pm) throws JavaModelException {
return null;
}
/**
* Open an <code>Openable</code> that is known to be closed (no check for <code>isOpen()</code>).
*/
protected void openWhenClosed(IProgressMonitor pm) throws JavaModelException {
try {
// 1) Parent must be open - open the parent if necessary
Openable openableParent = (Openable)getOpenableParent();
if (openableParent != null) {
OpenableElementInfo openableParentInfo = (OpenableElementInfo) fgJavaModelManager.getInfo((IJavaElement) openableParent);
if (openableParentInfo == null) {
openableParent.openWhenClosed(pm);
}
// Parent is open.
}
// 1.5) Ensure my resource is local
IResource resource = getCorrespondingResource();
if (resource != null) {
try {
JavaModelManager.getJavaModelManager().ensureLocal(resource);
} catch (CoreException e) {
throw new JavaModelException(e);
}
}
// 2) create the new element info and open a buffer
OpenableElementInfo info = createElementInfo();
if (resource != null && isSourceElement()) {
openBuffer(pm);
}
// 3) build the structure of the openable
buildStructure(info, pm);
// 4) anything special
opening(info);
// if any problems occuring openning the element, ensure that it's info
// does not remain in the cache (some elements, pre-cache their info
// as they are being opened).
} catch (JavaModelException e) {
fgJavaModelManager.removeInfo(this);
throw e;
}
}
/**
* @see IOpenable
*/
public void save(IProgressMonitor pm, boolean force) throws JavaModelException {
if (isReadOnly()) {
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.READ_ONLY, this));
}
IBuffer buf = getBuffer();
if (buf != null) { // some Openables (like a JavaProject) don't have a buffer
buf.save(pm, force);
this.makeConsistent(pm); // update the element info of this element
}
}
/**
* Find enclosing package fragment root if any
*/
public PackageFragmentRoot getPackageFragmentRoot() {
IJavaElement current = this;
do {
if (current instanceof PackageFragmentRoot) return (PackageFragmentRoot)current;
current = current.getParent();
} while(current != null);
return null;
}
}