/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.PerformanceStats;
import org.eclipse.jdt.core.BufferChangedEvent;
import org.eclipse.jdt.core.CompletionRequestor;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IBufferChangedListener;
import org.eclipse.jdt.core.IBufferFactory;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.internal.codeassist.CompletionEngine;
import org.eclipse.jdt.internal.codeassist.SelectionEngine;
import org.eclipse.jdt.internal.core.util.Util;
/**
* Abstract class for implementations of java elements which are IOpenable.
*
* @see IJavaElement
* @see IOpenable
*/
public abstract class Openable extends JavaElement implements IOpenable, IBufferChangedListener {
protected Openable(JavaElement parent) {
super(parent);
}
/**
* 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()) {
JavaModelManager.getJavaModelManager().getElementsOutOfSynchWithBuffers().remove(this);
getBufferManager().removeBuffer(event.getBuffer());
} else {
JavaModelManager.getJavaModelManager().getElementsOutOfSynchWithBuffers().add(this);
}
}
/**
* Builds this element's structure and properties in the given info object, based on this
* element's current contents (reuse 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 buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource) throws JavaModelException;
/*
* Returns whether this element can be removed from the Java model cache to make space.
*/
public boolean canBeRemovedFromCache() {
try {
return !hasUnsavedChanges();
} catch (JavaModelException e) {
return false;
}
}
/*
* Returns whether the buffer of this element can be removed from the Java model cache to make space.
*/
public boolean canBufferBeRemovedFromCache(IBuffer buffer) {
return !buffer.hasUnsavedChanges();
}
/**
* Close the buffer associated with this element, if any.
*/
protected void closeBuffer() {
if (!hasBuffer())
return; // nothing to do
IBuffer buffer= getBufferManager().getBuffer(this);
if (buffer != null) {
buffer.close();
buffer.removeBufferChangedListener(this);
}
}
/**
* This element is being closed. Do any necessary cleanup.
*/
protected void closing(Object info) {
closeBuffer();
}
protected void codeComplete(
org.eclipse.jdt.internal.compiler.env.ICompilationUnit cu,
org.eclipse.jdt.internal.compiler.env.ICompilationUnit unitToSkip,
int position, CompletionRequestor requestor,
WorkingCopyOwner owner,
ITypeRoot typeRoot,
IProgressMonitor monitor) throws JavaModelException {
if (requestor == null) {
throw new IllegalArgumentException("Completion requestor cannot be null"); //$NON-NLS-1$
}
PerformanceStats performanceStats= CompletionEngine.PERF
? PerformanceStats.getStats(JavaModelManager.COMPLETION_PERF, this)
: null;
if (performanceStats != null) {
performanceStats.startRun(new String(cu.getFileName()) + " at " + position); //$NON-NLS-1$
}
IBuffer buffer= getBuffer();
if (buffer == null) {
return;
}
if (position < -1 || position > buffer.getLength()) {
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS));
}
JavaProject project= (JavaProject)getJavaProject();
SearchableEnvironment environment= project.newSearchableNameEnvironment(owner);
// set unit to skip
environment.unitToSkip= unitToSkip;
// code complete
CompletionEngine engine= new CompletionEngine(environment, requestor, project.getOptions(true), project, owner, monitor);
engine.complete(cu, position, 0, typeRoot);
if (performanceStats != null) {
performanceStats.endRun();
}
if (NameLookup.VERBOSE) {
System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInSourcePackage: " + environment.nameLookup.timeSpentInSeekTypesInSourcePackage + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInBinaryPackage: " + environment.nameLookup.timeSpentInSeekTypesInBinaryPackage + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
protected IJavaElement[] codeSelect(org.eclipse.jdt.internal.compiler.env.ICompilationUnit cu, int offset, int length, WorkingCopyOwner owner) throws JavaModelException {
PerformanceStats performanceStats= SelectionEngine.PERF
? PerformanceStats.getStats(JavaModelManager.SELECTION_PERF, this)
: null;
if (performanceStats != null) {
performanceStats.startRun(new String(cu.getFileName()) + " at [" + offset + "," + length + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
JavaProject project= (JavaProject)getJavaProject();
SearchableEnvironment environment= project.newSearchableNameEnvironment(owner);
SelectionRequestor requestor= new SelectionRequestor(environment.nameLookup, this);
IBuffer buffer= getBuffer();
if (buffer == null) {
return requestor.getElements();
}
int end= buffer.getLength();
if (offset < 0 || length < 0 || offset + length > end) {
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS));
}
// fix for 1FVXGDK
SelectionEngine engine= new SelectionEngine(environment, requestor, project.getOptions(true), owner);
engine.select(cu, offset, offset + length - 1);
if (performanceStats != null) {
performanceStats.endRun();
}
if (NameLookup.VERBOSE) {
System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInSourcePackage: " + environment.nameLookup.timeSpentInSeekTypesInSourcePackage + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInBinaryPackage: " + environment.nameLookup.timeSpentInSeekTypesInBinaryPackage + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
return requestor.getElements();
}
/*
* Returns a new element info for this element.
*/
protected Object createElementInfo() {
return new OpenableElementInfo();
}
/**
* @see IJavaElement
*/
public boolean exists() {
if (JavaModelManager.getJavaModelManager().getInfo(this) != null)
return true;
switch (getElementType()) {
case IJavaElement.PACKAGE_FRAGMENT:
PackageFragmentRoot root= getPackageFragmentRoot();
if (root.isArchive()) {
// pkg in a jar -> need to open root to know if this pkg exists
JarPackageFragmentRootInfo rootInfo;
try {
rootInfo= (JarPackageFragmentRootInfo)root.getElementInfo();
} catch (JavaModelException e) {
return false;
}
return rootInfo.rawPackageInfo.containsKey(((PackageFragment)this).names);
}
break;
case IJavaElement.CLASS_FILE:
if (getPackageFragmentRoot().isArchive()) {
// class file in a jar -> need to open this class file to know if it exists
return super.exists();
}
break;
}
return validateExistence(resource()).isOK();
}
public String findRecommendedLineSeparator() throws JavaModelException {
IBuffer buffer= getBuffer();
String source= buffer == null ? null : buffer.getContents();
return Util.getLineSeparator(source, getJavaProject());
}
protected void generateInfos(Object info, HashMap newElements, IProgressMonitor monitor) throws JavaModelException {
if (JavaModelCache.VERBOSE) {
String element;
switch (getElementType()) {
case JAVA_PROJECT:
element= "project"; //$NON-NLS-1$
break;
case PACKAGE_FRAGMENT_ROOT:
element= "root"; //$NON-NLS-1$
break;
case PACKAGE_FRAGMENT:
element= "package"; //$NON-NLS-1$
break;
case CLASS_FILE:
element= "class file"; //$NON-NLS-1$
break;
case COMPILATION_UNIT:
element= "compilation unit"; //$NON-NLS-1$
break;
default:
element= "element"; //$NON-NLS-1$
}
System.out.println(Thread.currentThread() + " OPENING " + element + " " + this.toStringWithAncestors()); //$NON-NLS-1$//$NON-NLS-2$
}
// open its ancestors if needed
openAncestors(newElements, monitor);
// validate existence
IResource underlResource= resource();
IStatus status= validateExistence(underlResource);
if (!status.isOK())
throw newJavaModelException(status);
if (monitor != null && monitor.isCanceled())
throw new OperationCanceledException();
// puts the info before building the structure so that questions to the handle behave as if the element existed
// (case of compilation units becoming working copies)
newElements.put(this, info);
// build the structure of the openable (this will open the buffer if needed)
try {
OpenableElementInfo openableElementInfo= (OpenableElementInfo)info;
boolean isStructureKnown= buildStructure(openableElementInfo, monitor, newElements, underlResource);
openableElementInfo.setIsStructureKnown(isStructureKnown);
} catch (JavaModelException e) {
newElements.remove(this);
throw e;
}
// remove out of sync buffer for this element
JavaModelManager.getJavaModelManager().getElementsOutOfSynchWithBuffers().remove(this);
if (JavaModelCache.VERBOSE) {
System.out.println(JavaModelManager.getJavaModelManager().cacheToString("-> ")); //$NON-NLS-1$
}
}
/**
* 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
Object info= getElementInfo();
IBuffer buffer= getBufferManager().getBuffer(this);
if (buffer == null) {
// try to (re)open a buffer
buffer= openBuffer(null, info);
}
if (buffer instanceof NullBuffer) {
return null;
}
return buffer;
} else {
return null;
}
}
/**
* Answers the buffer factory to use for creating new buffers
*
* @deprecated
*/
public IBufferFactory getBufferFactory() {
return getBufferManager().getDefaultBufferFactory();
}
/**
* Returns the buffer manager for this element.
*/
protected BufferManager 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 IOpenable getOpenable() {
return this;
}
/**
* @see IJavaElement
*/
public IResource getUnderlyingResource() throws JavaModelException {
IResource parentResource= this.parent.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(getElementName());
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 IOpenable
*/
public boolean hasUnsavedChanges() throws JavaModelException {
if (isReadOnly() || !isOpen()) {
return false;
}
IBuffer buf= getBuffer();
if (buf != null && buf.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
int elementType= getElementType();
if (elementType == PACKAGE_FRAGMENT ||
elementType == PACKAGE_FRAGMENT_ROOT ||
elementType == JAVA_PROJECT ||
elementType == JAVA_MODEL) { // fix for 1FWNMHH
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() {
return true;
}
/**
*
* @see IOpenable
*/
public boolean isOpen() {
return JavaModelManager.getJavaModelManager().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 IJavaElement
*/
public boolean isStructureKnown() throws JavaModelException {
return ((OpenableElementInfo)getElementInfo()).isStructureKnown();
}
/**
* @see IOpenable
*/
public void makeConsistent(IProgressMonitor monitor) throws JavaModelException {
// only compilation units can be inconsistent
// other openables cannot be inconsistent so default is to do nothing
}
/**
* @see IOpenable
*/
public void open(IProgressMonitor pm) throws JavaModelException {
getElementInfo(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, Object info) throws JavaModelException {
return null;
}
public IResource getResource() {
PackageFragmentRoot root= getPackageFragmentRoot();
if (root != null) {
if (root.isExternal())
return null;
if (root.isArchive())
return root.resource(root);
}
return resource(root);
}
public IResource resource() {
PackageFragmentRoot root= getPackageFragmentRoot();
if (root != null && root.isArchive())
return root.resource(root);
return resource(root);
}
protected abstract IResource resource(PackageFragmentRoot root);
/**
* Returns whether the corresponding resource or associated file exists
*/
protected boolean resourceExists(IResource underlyingResource) {
return underlyingResource.isAccessible();
}
/**
* @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);
makeConsistent(pm); // update the element info of this element
}
}
/**
* Find enclosing package fragment root if any
*/
public PackageFragmentRoot getPackageFragmentRoot() {
return (PackageFragmentRoot)getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
}
/*
* Validates the existence of this openable. Returns a non ok status if it doesn't exist.
*/
abstract protected IStatus validateExistence(IResource underlyingResource);
/*
* Opens the ancestors of this openable that are not yet opened, validating their existence.
*/
protected void openAncestors(HashMap newElements, IProgressMonitor monitor) throws JavaModelException {
Openable openableParent= (Openable)getOpenableParent();
if (openableParent != null && !openableParent.isOpen()) {
openableParent.generateInfos(openableParent.createElementInfo(), newElements, monitor);
}
}
}