/*
* Author: C.Williams
*
* Copyright (c) 2004 RubyPeople.
*
* This file is part of the Ruby Development Tools (RDT) plugin for eclipse. You
* can get copy of the GPL along with further information about RubyPeople and
* third party software bundled with RDT in the file
* org.rubypeople.rdt.core_x.x.x/RDT.license or otherwise at
* http://www.rubypeople.org/RDT.license.
*
* RDT is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* RDT is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* RDT; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
* Suite 330, Boston, MA 02111-1307 USA
*/
package org.rubypeople.rdt.internal.core;
import java.util.ArrayList;
import java.util.HashMap;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.PlatformObject;
import org.jruby.ast.Node;
import org.rubypeople.rdt.core.IField;
import org.rubypeople.rdt.core.IOpenable;
import org.rubypeople.rdt.core.IParent;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyModel;
import org.rubypeople.rdt.core.IRubyModelStatus;
import org.rubypeople.rdt.core.IRubyModelStatusConstants;
import org.rubypeople.rdt.core.IRubyProject;
import org.rubypeople.rdt.core.IRubyScript;
import org.rubypeople.rdt.core.ISourceRange;
import org.rubypeople.rdt.core.ISourceReference;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.core.WorkingCopyOwner;
import org.rubypeople.rdt.internal.core.util.MementoTokenizer;
import org.rubypeople.rdt.internal.core.util.Util;
/**
* @author Chris
*
*/
public abstract class RubyElement extends PlatformObject implements IRubyElement {
public static final char JEM_ESCAPE = '\\';
public static final char JEM_RUBYPROJECT = '=';
public static final char JEM_SOURCEFOLDERROOT = '/';
public static final char JEM_SOURCE_FOLDER = '<';
public static final char JEM_FIELD = '^';
public static final char JEM_METHOD = '~';
public static final char JEM_RUBYSCRIPT = '{';
public static final char JEM_TYPE = '[';
public static final char JEM_IMPORTDECLARATION = '#';
public static final char JEM_COUNT = '!';
public static final char JEM_LOCALVARIABLE = '@';
public static final IRubyElement[] NO_ELEMENTS = new IRubyElement[0];
protected static final Object NO_INFO = new Object();
protected RubyElement parent;
public RubyElement(RubyElement parent) {
this.parent = parent;
}
public String getElementName() {
return ""; //$NON-NLS-1$
}
/**
* Returns true if this handle represents the same Ruby 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 (this == o) return true;
// Ruby model parent is null
if (this.parent == null) return super.equals(o);
// assume instanceof check is done in subclass
RubyElement other = (RubyElement) o;
return getElementName().equals(other.getElementName()) && this.parent.equals(other.parent);
}
/**
* @see IRubyElement
*/
public boolean exists() {
try {
getElementInfo();
return true;
} catch (RubyModelException e) {
// element doesn't exist: return false
}
return false;
}
public abstract int getElementType();
void setParent(RubyElement parent) {
this.parent = parent;
}
/*
* (non-Javadoc)
*
* @see org.rubypeople.rdt.core.IRubyElement#getParent()
*/
public IRubyElement getParent() {
return parent;
}
/*
* @see IRubyElement#getPrimaryElement()
*/
public IRubyElement getPrimaryElement() {
return getPrimaryElement(true);
}
/*
* Returns the primary element. If checkOwner, and the cu owner is primary,
* return this element.
*/
public IRubyElement getPrimaryElement(boolean checkOwner) {
return this;
}
/**
* Returns the element that is located at the given source position
* in this element. This is a helper method for <code>IRubyScript#getElementAt</code>,
* and only works on ruby scripts 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 IRubyElement getSourceElementAt(int position) throws RubyModelException {
if (this instanceof ISourceReference) {
IRubyElement[] children = getChildren();
for (int i = children.length-1; i >= 0; i--) {
IRubyElement aChild = children[i];
if (aChild instanceof SourceRefElement) {
SourceRefElement child = (SourceRefElement) children[i];
ISourceRange range = child.getSourceRange();
int start = range.getOffset();
int end = start + range.getLength();
if (start <= position && position <= end) {
if (child instanceof IField) {
// check muti-declaration case (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=39943)
int declarationStart = start;
SourceRefElement candidate = null;
do {
// check name range
range = ((IField)child).getNameRange();
if (position <= range.getOffset() + range.getLength()) {
candidate = child;
} else {
return candidate == null ? child.getSourceElementAt(position) : candidate.getSourceElementAt(position);
}
child = --i>=0 ? (SourceRefElement) children[i] : null;
} while (child != null && child.getSourceRange().getOffset() == declarationStart);
// position in field's type: use first field
return candidate.getSourceElementAt(position);
} else if (child instanceof IParent) {
return child.getSourceElementAt(position);
} else {
return child;
}
}
}
}
} else {
// should not happen
Assert.isTrue(false);
}
return this;
}
/**
* @see IRubyElement
*/
public IRubyElement getAncestor(int ancestorType) {
IRubyElement element = this;
while (element != null) {
if (element.getElementType() == ancestorType) return element;
element = element.getParent();
}
return null;
}
/**
* @see IParent
*/
public IRubyElement[] getChildren() throws RubyModelException {
Object elementInfo = getElementInfo();
if (elementInfo instanceof RubyElementInfo) {
return ((RubyElementInfo) elementInfo).getChildren();
}
return NO_ELEMENTS;
}
/**
* Returns a collection of (immediate) children of this node of the
* specified type.
*
* @param type - one of the type constants defined by RubyElement
*/
public ArrayList<IRubyElement> getChildrenOfType(int type) throws RubyModelException {
IRubyElement[] children = getChildren();
int size = children.length;
ArrayList<IRubyElement> list = new ArrayList<IRubyElement>(size);
for (int i = 0; i < size; ++i) {
RubyElement elt = (RubyElement)children[i];
if (elt.getElementType() == type) {
list.add(elt);
}
}
return list;
}
/**
* @see IParent
*/
public boolean hasChildren() throws RubyModelException {
// if I am not open, return true to avoid opening (case of a Java
// project, a compilation unit or a class file).
// also see https://bugs.eclipse.org/bugs/show_bug.cgi?id=52474
Object elementInfo = RubyModelManager.getRubyModelManager().getInfo(this);
if (elementInfo instanceof RubyElementInfo) {
return ((RubyElementInfo) elementInfo).getChildren().length > 0;
}
return true;
}
/**
* Returns the hash code for this Ruby 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 (this.parent == null) return super.hashCode();
return Util.combineHashCodes(getElementName().hashCode(), this.parent.hashCode());
}
/*
* (non-Javadoc)
*
* @see org.rubypeople.rdt.core.IRubyElement#isType(int)
*/
public boolean isType(int type) {
return type == getElementType();
}
/**
* @return
*/
public IRubyScript getRubyScript() {
return null;
}
public IRubyProject getRubyProject() {
IRubyElement current = this;
do {
if (current instanceof IRubyProject) return (IRubyProject) current;
} while ((current = current.getParent()) != null);
return null;
}
public boolean isReadOnly() {
return false;
}
/**
* @see IRubyElement
*/
public IRubyModel getRubyModel() {
IRubyElement current = this;
do {
if (current instanceof IRubyModel) return (IRubyModel) current;
} while ((current = current.getParent()) != null);
return null;
}
/**
* @see IOpenable
*/
public void close() throws RubyModelException {
RubyModelManager.getRubyModelManager().removeInfoAndChildren(this);
}
/**
* This element is being closed. Do any necessary cleanup.
*
* @throws RubyModelException
*/
protected abstract void closing(Object info) throws RubyModelException;
/**
* Returns true if this element is an ancestor of the given element,
* otherwise false.
*/
public boolean isAncestorOf(IRubyElement e) {
IRubyElement parentElement = e.getParent();
while (parentElement != null && !parentElement.equals(this)) {
parentElement = parentElement.getParent();
}
return parentElement != null;
}
/*
* Opens an <code> Openable </code> that is known to be closed (no check for
* <code> isOpen() </code> ). Returns the created element info.
*/
protected Object openWhenClosed(Object info, IProgressMonitor monitor) throws RubyModelException {
RubyModelManager manager = RubyModelManager.getRubyModelManager();
boolean hadTemporaryCache = manager.hasTemporaryCache();
try {
HashMap newElements = manager.getTemporaryCache();
generateInfos(info, newElements, monitor);
if (info == null) {
info = newElements.get(this);
}
if (info == null) { // a source ref element could not be opened
// close the buffer that was opened for the openable parent
// close only the openable's buffer (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=62854)
Openable openable = (Openable) getOpenable();
if (newElements.containsKey(openable)) {
openable.closeBuffer();
}
throw newNotPresentException();
}
if (!hadTemporaryCache) {
manager.putInfos(this, newElements);
}
} finally {
if (!hadTemporaryCache) {
manager.resetTemporaryCache();
}
}
return info;
}
/**
* Creates and returns a new not present exception for this element.
*/
public RubyModelException newNotPresentException() {
return new RubyModelException(new RubyModelStatus(IRubyModelStatusConstants.ELEMENT_DOES_NOT_EXIST, this));
}
/**
* Creates and returns a new Ruby model exception for this element with the
* given status.
*/
public RubyModelException newRubyModelException(IStatus status) {
if (status instanceof IRubyModelStatus)
return new RubyModelException((IRubyModelStatus) status);
return new RubyModelException(new RubyModelStatus(status.getSeverity(), status.getCode(), status.getMessage()));
}
/**
* @param newElements
* @param info
* @param monitor
*/
abstract protected void generateInfos(Object info, HashMap newElements, IProgressMonitor monitor) throws RubyModelException;
/*
* @see IRubyElement
*/
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) this.parent;
}
/**
*/
public String readableName() {
return this.getElementName();
}
/**
* 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. NOTE: BinaryType
* infos are NOT rooted under RubyElementInfo.
*
* @exception RubyModelException
* if the element is not present or not accessible
*/
public Object getElementInfo() throws RubyModelException {
return getElementInfo(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 RubyModelException
* if the element is not present or not accessible
*/
public Object getElementInfo(IProgressMonitor monitor) throws RubyModelException {
RubyModelManager manager = RubyModelManager.getRubyModelManager();
Object info = manager.getInfo(this);
if (info != null) return info;
return openWhenClosed(createElementInfo(), monitor);
}
/*
* Returns a new element info for this element.
*/
protected abstract Object createElementInfo();
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(boolean showResolvedInfo) {
StringBuffer buffer = new StringBuffer();
this.toStringInfo(0, buffer, NO_INFO, showResolvedInfo);
this.toStringAncestors(buffer);
return buffer.toString();
}
/**
* Debugging purposes
*
* @param showResolvedInfo
*
*/
protected void toStringInfo(int tab, StringBuffer buffer, Object info,
boolean showResolvedInfo) {
buffer.append(this.tabString(tab));
toStringName(buffer);
if (info == null) {
buffer.append(" (not open)"); //$NON-NLS-1$
}
}
/**
* 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) {
RubyElement parentElement = (RubyElement) this.getParent();
if (parentElement != null && parentElement.getParent() != null) {
buffer.append(" [in "); //$NON-NLS-1$
parentElement.toStringInfo(0, buffer, NO_INFO);
parentElement.toStringAncestors(buffer);
buffer.append("]"); //$NON-NLS-1$
}
}
/**
* Debugging purposes
*/
protected void toStringChildren(int tab, StringBuffer buffer, Object info) {
if (info == null || !(info instanceof RubyElementInfo)) return;
IRubyElement[] children = ((RubyElementInfo) info).getChildren();
for (int i = 0; i < children.length; i++) {
buffer.append("\n"); //$NON-NLS-1$
((RubyElement) children[i]).toString(tab + 1, buffer);
}
}
/**
* Debugging purposes
*/
public Object toStringInfo(int tab, StringBuffer buffer) {
Object info = RubyModelManager.getRubyModelManager().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));
toStringName(buffer);
if (info == null) {
buffer.append(" (not open)"); //$NON-NLS-1$
}
}
/**
* Debugging purposes
*/
protected void toStringName(StringBuffer buffer) {
buffer.append(getElementName());
}
public Node findNode(Node cuAST) {
return null; // works only inside a ruby script
}
/*
* Creates a Ruby element handle from the given memento.
* The given token is the current delimiter indicating the type of the next token(s).
* The given working copy owner is used only for ruby script handles.
*/
public abstract IRubyElement getHandleFromMemento(String token, MementoTokenizer memento, WorkingCopyOwner owner);
/*
* Creates a Ruby element handle from the given memento.
* The given working copy owner is used only for ruby script handles.
*/
public IRubyElement getHandleFromMemento(MementoTokenizer memento, WorkingCopyOwner owner) {
if (!memento.hasMoreTokens()) return this;
String token = memento.nextToken();
return getHandleFromMemento(token, memento, owner);
}
/**
* @see IJavaElement
*/
public String getHandleIdentifier() {
return getHandleMemento();
}
/**
* @see JavaElement#getHandleMemento()
*/
public String getHandleMemento(){
StringBuffer buff = new StringBuffer();
getHandleMemento(buff);
return buff.toString();
}
protected void getHandleMemento(StringBuffer buff) {
((RubyElement)getParent()).getHandleMemento(buff);
buff.append(getHandleMementoDelimiter());
escapeMementoName(buff, getElementName());
}
/**
* Returns the <code>char</code> that marks the start of this handles
* contribution to a memento.
*/
protected abstract char getHandleMementoDelimiter();
protected void escapeMementoName(StringBuffer buffer, String mementoName) {
for (int i = 0, length = mementoName.length(); i < length; i++) {
char character = mementoName.charAt(i);
switch (character) {
case JEM_ESCAPE:
case JEM_COUNT:
case JEM_RUBYPROJECT:
case JEM_SOURCEFOLDERROOT:
case JEM_SOURCE_FOLDER:
case JEM_FIELD:
case JEM_METHOD:
case JEM_RUBYSCRIPT:
case JEM_TYPE:
case JEM_IMPORTDECLARATION:
case JEM_LOCALVARIABLE:
buffer.append(JEM_ESCAPE);
}
buffer.append(character);
}
}
public IRubyElement unresolved() {
return this;
}
}