/*
* Created on Jan 13, 2005
*/
package org.rubypeople.rdt.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.OperationCanceledException;
import org.rubypeople.rdt.core.BufferChangedEvent;
import org.rubypeople.rdt.core.IBuffer;
import org.rubypeople.rdt.core.IBufferChangedListener;
import org.rubypeople.rdt.core.IOpenable;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyModelStatusConstants;
import org.rubypeople.rdt.core.IRubyScript;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.core.WorkingCopyOwner;
import org.rubypeople.rdt.internal.codeassist.SelectionEngine;
import org.rubypeople.rdt.internal.core.buffer.BufferManager;
import org.rubypeople.rdt.internal.core.util.Util;
/**
* @author cawilliams
*
*/
public abstract class Openable extends RubyElement implements IOpenable, IBufferChangedListener {
/**
* @param parent
*/
public Openable(RubyElement parent) {
super(parent);
}
/**
* Find enclosing package fragment root if any
*/
public SourceFolderRoot getSourceFolderRoot() {
return (SourceFolderRoot) getAncestor(IRubyElement.SOURCE_FOLDER_ROOT);
}
/**
* @see IRubyElement
*/
public IResource getUnderlyingResource() throws RubyModelException {
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;
}
}
/**
* 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()) {
RubyModelManager.getRubyModelManager().getElementsOutOfSynchWithBuffers().remove(this);
getBufferManager().removeBuffer(event.getBuffer());
} else {
RubyModelManager.getRubyModelManager().getElementsOutOfSynchWithBuffers().add(this);
}
}
/**
* Note: a buffer with no unsaved changes can be closed by the Ruby 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 RubyModelException {
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);
}
return buffer;
}
return null;
}
/**
* 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.
* @param info
*/
protected IBuffer openBuffer(IProgressMonitor pm, Object info) throws RubyModelException {
return null;
}
/*
* Returns whether the buffer of this element can be removed from the Ruby
* model cache to make space.
*/
public boolean canBufferBeRemovedFromCache(IBuffer buffer) {
return !buffer.hasUnsavedChanges();
}
/**
* 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 isOpen() {
return RubyModelManager.getRubyModelManager().getInfo(this) != null;
}
/**
* 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 IRubyElement
*/
public IResource getCorrespondingResource() throws RubyModelException {
return getUnderlyingResource();
}
/*
* Returns whether this element can be removed from the Ruby model cache to
* make space.
*/
public boolean canBeRemovedFromCache() {
try {
return !hasUnsavedChanges();
} catch (RubyModelException e) {
return false;
}
}
/**
* Subclasses must override as required.
*
* @see IOpenable
*/
public boolean isConsistent() {
return true;
}
/**
* @see IRubyElement
*/
public boolean isStructureKnown() throws RubyModelException {
return ((OpenableElementInfo) getElementInfo()).isStructureKnown();
}
/**
* @see IOpenable
*/
public boolean hasUnsavedChanges() throws RubyModelException {
if (isReadOnly() || !isOpen()) { return false; }
IBuffer buf = this.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 == RUBY_PROJECT || elementType == RUBY_MODEL) { // fix for
// 1FWNMHH
Enumeration openBuffers = getBufferManager().getOpenBuffers();
while (openBuffers.hasMoreElements()) {
IBuffer buffer = (IBuffer) openBuffers.nextElement();
if (buffer.hasUnsavedChanges()) {
IRubyElement owner = (IRubyElement) buffer.getOwner();
if (isAncestorOf(owner)) { return true; }
}
}
}
return false;
}
/**
* This element is being closed. Do any necessary cleanup.
*/
protected void closing(Object info) {
closeBuffer();
}
/**
* @see IRubyElement
*/
public boolean exists() {
RubyModelManager manager = RubyModelManager.getRubyModelManager();
if (manager.getInfo(this) != null) return true;
if (!parentExists()) return false;
SourceFolderRoot root = getSourceFolderRoot();
if (root != null
&& (root == this || !root.isExternal())) {
return resourceExists();
}
return super.exists();
}
/**
* Answers true if the parent exists (null parent is answering true)
*
*/
protected boolean parentExists() {
IRubyElement parentElement = getParent();
if (parentElement == null) return true;
return parentElement.exists();
}
/**
* 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);
}
}
protected void generateInfos(Object info, HashMap newElements, IProgressMonitor monitor) throws RubyModelException {
if (RubyModelManager.isVerbose()) {
String element;
switch (getElementType()) {
case RUBY_PROJECT:
element = "project"; //$NON-NLS-1$
break;
case SCRIPT:
element = "script"; //$NON-NLS-1$
break;
default:
element = "element"; //$NON-NLS-1$
}
System.out.println(Thread.currentThread() + " OPENING " + element + " " + this.toString()); //$NON-NLS-1$//$NON-NLS-2$
}
// open the parent if necessary
openParent(info, newElements, monitor);
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, getResource());
openableElementInfo.setIsStructureKnown(isStructureKnown);
} catch (RubyModelException e) {
newElements.remove(this);
throw e;
}
// remove out of sync buffer for this element
RubyModelManager.getRubyModelManager().getElementsOutOfSynchWithBuffers().remove(this);
if (RubyModelManager.isVerbose()) {
System.out.println(RubyModelManager.getRubyModelManager().cache.toStringFillingRation("-> ")); //$NON-NLS-1$
}
}
/**
* 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 RubyModelException;
/**
* Open the parent element if necessary.
*
* @param newElements
* @param info
*/
protected void openParent(Object info, HashMap newElements, IProgressMonitor pm) throws RubyModelException {
Openable openableParent = (Openable) getOpenableParent();
if (openableParent != null && !openableParent.isOpen()) {
openableParent.generateInfos(openableParent.createElementInfo(), newElements, pm);
}
}
/**
* @see IOpenable
*/
public void makeConsistent(IProgressMonitor monitor) throws RubyModelException {
// only scripts can be inconsistent
}
/**
* @see IOpenable
*/
public void save(IProgressMonitor pm, boolean force) throws RubyModelException {
if (isReadOnly()) { throw new RubyModelException(new RubyModelStatus(IRubyModelStatusConstants.READ_ONLY, this)); }
IBuffer buf = getBuffer();
if (buf != null) { // some Openables (like a RubyProject) don't have a
// buffer
buf.save(pm, force);
this.makeConsistent(pm); // update the element info of this
// element
}
}
/**
* Returns whether the corresponding resource or associated file exists
*/
protected boolean resourceExists() {
return
RubyModel.getTarget(
this.getPath().makeRelative(), // ensure path is relative (see http://dev.eclipse.org/bugs/show_bug.cgi?id=22517)
true) != null;
}
/**
* @see IOpenable
*/
public void open(IProgressMonitor pm) throws RubyModelException {
getElementInfo(pm);
}
protected IRubyElement[] codeSelect(IRubyScript cu, int offset, int length, WorkingCopyOwner owner) throws RubyModelException {
IBuffer buffer = getBuffer();
if (buffer == null) {
return new IRubyElement[0];
}
int end= buffer.getLength();
if (offset < 0 || length < 0 || offset + length > end ) {
throw new RubyModelException(new RubyModelStatus(IRubyModelStatusConstants.INDEX_OUT_OF_BOUNDS));
}
SelectionEngine engine = new SelectionEngine();
return engine.select(cu, offset, offset + length - 1);
}
public String findRecommendedLineSeparator() throws RubyModelException {
IBuffer buffer = getBuffer();
String source = buffer == null ? null : buffer.getContents();
return Util.getLineSeparator(source, getRubyProject());
}
}