| /******************************************************************************* |
| * Copyright (c) 2005, 2016 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 |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.core; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.PlatformObject; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.IField; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.IModelElementMemento; |
| import org.eclipse.dltk.core.IModelElementVisitor; |
| import org.eclipse.dltk.core.IModelElementVisitorExtension; |
| import org.eclipse.dltk.core.IModelStatus; |
| import org.eclipse.dltk.core.IModelStatusConstants; |
| import org.eclipse.dltk.core.IOpenable; |
| import org.eclipse.dltk.core.IParent; |
| import org.eclipse.dltk.core.IScriptModel; |
| import org.eclipse.dltk.core.IScriptProject; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ISourceRange; |
| import org.eclipse.dltk.core.ISourceReference; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.ScriptModelUtil; |
| import org.eclipse.dltk.core.WorkingCopyOwner; |
| import org.eclipse.dltk.internal.core.util.MementoTokenizer; |
| import org.eclipse.dltk.internal.core.util.Util; |
| import org.eclipse.dltk.utils.CorePrinter; |
| |
| /** |
| * Root of model element handle hierarchy. |
| * |
| * @see IModelElement |
| */ |
| |
| public abstract class ModelElement extends PlatformObject implements |
| IModelElement, IModelElementMemento { |
| |
| public static final char JEM_ESCAPE = '\\'; |
| public static final char JEM_SCRIPTPROJECT = '='; |
| public static final char JEM_PROJECTFRAGMENT = '/'; |
| public static final char JEM_SCRIPTFOLDER = '<'; |
| public static final char JEM_FIELD = '^'; |
| public static final char JEM_METHOD = '~'; |
| public static final char JEM_SOURCEMODULE = '{'; |
| 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 char JEM_TYPE_PARAMETER = ']'; |
| public static final char JEM_PACKAGEDECLARATION = '%'; |
| |
| /** |
| * This Item is for direct user element handle. Resolving of elements with |
| * such delimiter requires building of the model. |
| */ |
| public static final char JEM_USER_ELEMENT = '}'; |
| public static final String JEM_USER_ELEMENT_ENDING = "=/<^~{[&!@]%}"; |
| |
| // Used to replace path / or \\ symbols in external package names and |
| // archives. |
| public static final char JEM_SKIP_DELIMETER = '>'; |
| |
| /** |
| * This element's parent, or <code>null</code> if this element does not have |
| * a parent. |
| */ |
| protected final ModelElement parent; |
| |
| protected static final ModelElement[] NO_ELEMENTS = new ModelElement[0]; |
| protected static final Object NO_INFO = new Object(); |
| |
| /** |
| * Constructs a handle for a model element with the given parent element. |
| * |
| * @param parent |
| * The parent of model element |
| * |
| * @exception IllegalArgumentException |
| * if the type is not one of the valid model element type |
| * constants |
| * |
| */ |
| protected ModelElement(ModelElement parent) throws IllegalArgumentException { |
| this.parent = parent; |
| } |
| |
| /** |
| * @see IModelElement |
| */ |
| @Override |
| public boolean exists() { |
| |
| try { |
| getElementInfo(); |
| return true; |
| } catch (ModelException e) { |
| // element doesn't exist: return false |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @see IModelElement |
| */ |
| @Override |
| public IModelElement getAncestor(int ancestorType) { |
| |
| IModelElement element = this; |
| while (element != null) { |
| if (element.getElementType() == ancestorType) |
| return element; |
| element = element.getParent(); |
| } |
| return null; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <E extends IModelElement> E getAncestor(Class<E> elementClass) { |
| IModelElement element = this; |
| do { |
| if (elementClass.isInstance(element)) { |
| return (E) element; |
| } |
| element = element.getParent(); |
| } while (element != null); |
| return null; |
| } |
| |
| /** |
| * @see IOpenable |
| */ |
| public void close() throws ModelException { |
| ModelManager.getModelManager().removeInfoAndChildren(this); |
| } |
| |
| /** |
| * This element is being closed. Do any necessary cleanup. |
| */ |
| protected abstract void closing(Object info) throws ModelException; |
| |
| /** |
| * 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 ModelElementInfo. |
| * |
| * @exception ModelException |
| * if the element is not present or not accessible |
| */ |
| public Object getElementInfo() throws ModelException { |
| 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. NOTE: BinaryType |
| * infos are NOT rooted under ModelElementInfo. |
| * |
| * @exception ModelException |
| * if the element is not present or not accessible |
| */ |
| public Object getElementInfo(IProgressMonitor monitor) |
| throws ModelException { |
| |
| ModelManager manager = ModelManager.getModelManager(); |
| Object info = manager.getInfo(this); |
| if (info != null) |
| return info; |
| return openWhenClosed(createElementInfo(), monitor); |
| } |
| |
| /* |
| * 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 ModelException { |
| ModelManager manager = ModelManager.getModelManager(); |
| 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; |
| } |
| |
| /* |
| * @see IModelElement |
| */ |
| @Override |
| 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; |
| } |
| |
| /** |
| * @see IModelElement |
| */ |
| @Override |
| public IModelElement getParent() { |
| return this.parent; |
| } |
| |
| /* |
| * Returns a new element info for this element. |
| */ |
| protected abstract Object createElementInfo(); |
| |
| /** |
| * Generates the element infos for this element, its ancestors (if they are |
| * not opened) and its children (if it is an Openable). Puts the newly |
| * created element info in the given map. |
| */ |
| protected abstract void generateInfos(Object info, HashMap newElements, |
| IProgressMonitor pm) throws ModelException; |
| |
| /** |
| * @see IAdaptable |
| */ |
| @Override |
| public String getElementName() { |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Creates and returns a new not present exception for this element. |
| */ |
| public ModelException newNotPresentException() { |
| return new ModelException(new ModelStatus( |
| IModelStatusConstants.ELEMENT_DOES_NOT_EXIST, this)); |
| } |
| |
| /** |
| * Returns true if this handle represents the same model 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 |
| */ |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) |
| return true; |
| // model parent is null |
| if (this.parent == null) { |
| return super.equals(o); |
| } |
| if (o == null) { |
| return false; |
| } |
| // assume instanceof check is done in subclass |
| final ModelElement other = (ModelElement) o; |
| return getElementName().equals(other.getElementName()) |
| && this.parent.equals(other.parent); |
| } |
| |
| /** |
| * Returns the hash code for this model 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. |
| */ |
| @Override |
| public int hashCode() { |
| if (this.parent == null) |
| return super.hashCode(); |
| return Util.combineHashCodes(getElementName().hashCode(), this.parent |
| .hashCode()); |
| } |
| |
| /** |
| * Returns true if this element is an ancestor of the given element, |
| * otherwise false. |
| */ |
| public boolean isAncestorOf(IModelElement e) { |
| IModelElement parentElement = e.getParent(); |
| while (parentElement != null && !parentElement.equals(this)) { |
| parentElement = parentElement.getParent(); |
| } |
| return parentElement != null; |
| } |
| |
| /** |
| * @see IModelElement |
| */ |
| @Override |
| public boolean isReadOnly() { |
| return false; |
| } |
| |
| /** |
| * Returns a collection of (immediate) children of this node of the |
| * specified type. |
| * |
| * @param type |
| * - one of the EM_* constants defined by ModelElement |
| */ |
| protected List<IModelElement> getChildrenOfType(int type) |
| throws ModelException { |
| return getChildrenOfType(type, null); |
| } |
| |
| protected List<IModelElement> getChildrenOfType(int type, |
| IProgressMonitor monitor) throws ModelException { |
| IModelElement[] children = getChildren(monitor); |
| int size = children.length; |
| List<IModelElement> list = new ArrayList<IModelElement>(size); |
| for (int i = 0; i < size; ++i) { |
| IModelElement elt = children[i]; |
| if (elt.getElementType() == type) { |
| list.add(elt); |
| } |
| } |
| return list; |
| } |
| |
| /** |
| * @see IParent |
| */ |
| public IModelElement[] getChildren() throws ModelException { |
| return getChildren(null); |
| } |
| |
| public IModelElement[] getChildren(IProgressMonitor monitor) |
| throws ModelException { |
| Object elementInfo = getElementInfo(monitor); |
| if (elementInfo instanceof ModelElementInfo) { |
| return ((ModelElementInfo) elementInfo).getChildren(); |
| } else { |
| return NO_ELEMENTS; |
| } |
| } |
| |
| /** |
| * @see IModelElement |
| */ |
| @Override |
| public IScriptModel getModel() { |
| IModelElement current = this; |
| do { |
| if (current instanceof IScriptModel) |
| return (IScriptModel) current; |
| } while ((current = current.getParent()) != null); |
| return null; |
| } |
| |
| /** |
| * Creates and returns a new model exception for this element with the given |
| * status. |
| */ |
| public ModelException newModelException(IStatus status) { |
| if (status instanceof IModelStatus) |
| return new ModelException((IModelStatus) status); |
| else |
| return new ModelException(new ModelStatus(status.getSeverity(), |
| status.getCode(), status.getMessage())); |
| } |
| |
| /** |
| * @see IModelElement |
| */ |
| @Override |
| public IScriptProject getScriptProject() { |
| IModelElement current = this; |
| do { |
| if (current instanceof IScriptProject) |
| return (IScriptProject) current; |
| } while ((current = current.getParent()) != null); |
| return null; |
| } |
| |
| public boolean hasChildren() throws ModelException { |
| // if I am not open, return true to avoid opening (case of a project, a |
| // source module or a binary file). |
| Object elementInfo = ModelManager.getModelManager().getInfo(this); |
| if (elementInfo instanceof ModelElementInfo) { |
| return ((ModelElementInfo) elementInfo).getChildren().length > 0; |
| } else { |
| return true; |
| } |
| } |
| |
| /** |
| * Debugging purposes |
| */ |
| 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, true/* show resolved info */); |
| return buffer.toString(); |
| } |
| |
| /** |
| * Debugging purposes |
| */ |
| @Override |
| 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() { |
| return toStringWithAncestors(true/* show resolved 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 |
| */ |
| protected void toStringAncestors(StringBuffer buffer) { |
| ModelElement parentElement = (ModelElement) this.getParent(); |
| if (parentElement != null && parentElement.getParent() != null) { |
| buffer.append(" [in "); //$NON-NLS-1$ |
| parentElement.toStringInfo(0, buffer, NO_INFO, false); // don't show |
| // resolved |
| // 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 ModelElementInfo)) |
| return; |
| IModelElement[] children = ((ModelElementInfo) info).getChildren(); |
| for (int i = 0; i < children.length; i++) { |
| buffer.append("\n"); //$NON-NLS-1$ |
| ((ModelElement) children[i]).toString(tab + 1, buffer); |
| } |
| } |
| |
| /** |
| * Debugging purposes |
| */ |
| public Object toStringInfo(int tab, StringBuffer buffer) { |
| Object info = ModelManager.getModelManager().peekAtInfo(this); |
| this.toStringInfo(tab, buffer, info, true/* show resolved info */); |
| return info; |
| } |
| |
| /** |
| * Debugging purposes |
| * |
| * @param showResolvedInfo |
| * TODO |
| */ |
| 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 |
| */ |
| protected void toStringName(StringBuffer buffer) { |
| buffer.append(getElementName()); |
| } |
| |
| /** |
| * Returns the element that is located at the given source position in this |
| * element. This is a helper method for |
| * <code>ISourceModule#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 IModelElement getSourceElementAt(int position) |
| throws ModelException { |
| IModelElement res = getSourceElementAtTop(position); |
| if (res != this) |
| return res; |
| |
| if (this instanceof ISourceReference) { |
| IModelElement[] children = getChildren(); |
| for (int i = children.length - 1; i >= 0; i--) { |
| IModelElement aChild = children[i]; |
| if (aChild instanceof SourceRefElement) { |
| SourceRefElement child = (SourceRefElement) children[i]; |
| if (child instanceof IParent) { |
| res = child.getSourceElementAt(position); |
| if (res != child) |
| return res; |
| } |
| } |
| } |
| } else { |
| // should not happen |
| Assert.isTrue(false); |
| } |
| return this; |
| } |
| |
| /** |
| * Returns the element that is located at the given source position in this |
| * element. This is a helper method for |
| * <code>ISourceModule#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 IModelElement getSourceElementAtTop(int position) |
| throws ModelException { |
| if (this instanceof ISourceReference) { |
| IModelElement[] children = getChildren(); |
| for (int i = children.length - 1; i >= 0; i--) { |
| IModelElement 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 instanceof IField |
| && 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; |
| } |
| |
| /** |
| * Returns element type and name. called from |
| * {@link #printNode(CorePrinter)} only. |
| * |
| * @return |
| */ |
| protected String describeElement() { |
| return ScriptModelUtil.describeElementType(getElementType()) + ':' |
| + getElementName(); |
| } |
| |
| /** |
| * Only for testing. Used to print this node with all sub childs. |
| * |
| * @param output |
| */ |
| public void printNode(CorePrinter output) { |
| output.formatPrint(describeElement()); |
| output.indent(); |
| try { |
| IModelElement modelElements[] = this.getChildren(); |
| for (int i = 0; i < modelElements.length; ++i) { |
| IModelElement element = modelElements[i]; |
| if (element instanceof ModelElement) { |
| ((ModelElement) element).printNode(output); |
| } else { |
| output.print("Unknown element:" + element); //$NON-NLS-1$ |
| } |
| } |
| } catch (ModelException ex) { |
| output.formatPrint(ex.getLocalizedMessage()); |
| } |
| output.dedent(); |
| } |
| |
| @Override |
| public IModelElement getPrimaryElement() { |
| return getPrimaryElement(true); |
| } |
| |
| /* |
| * Returns the primary element. If checkOwner, and the cu owner is primary, |
| * return this element. |
| */ |
| public IModelElement getPrimaryElement(boolean checkOwner) { |
| return this; |
| } |
| |
| @Override |
| public IModelElement getHandleFromMemento(MementoTokenizer memento, |
| WorkingCopyOwner owner) { |
| if (!memento.hasMoreTokens()) |
| return this; |
| String token = memento.nextToken(); |
| return getHandleFromMemento(token, memento, owner); |
| } |
| |
| @Override |
| public abstract IModelElement getHandleFromMemento(String token, |
| MementoTokenizer memento, WorkingCopyOwner owner); |
| |
| @Override |
| public String getHandleIdentifier() { |
| return getHandleMemento(); |
| } |
| |
| public String getHandleMemento() { |
| StringBuffer buff = new StringBuffer(); |
| getHandleMemento(buff); |
| return buff.toString(); |
| } |
| |
| public void getHandleMemento(StringBuffer buff) { |
| ((ModelElement) getParent()).getHandleMemento(buff); |
| buff.append(getHandleMementoDelimiter()); |
| escapeMementoName(buff, getElementName()); |
| } |
| |
| 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_SCRIPTPROJECT: |
| case JEM_PROJECTFRAGMENT: |
| case JEM_SCRIPTFOLDER: |
| case JEM_FIELD: |
| case JEM_METHOD: |
| case JEM_SOURCEMODULE: |
| case JEM_TYPE: |
| case JEM_IMPORTDECLARATION: |
| case JEM_LOCALVARIABLE: |
| case JEM_TYPE_PARAMETER: |
| case JEM_USER_ELEMENT: |
| buffer.append(JEM_ESCAPE); |
| } |
| buffer.append(character); |
| } |
| } |
| |
| public ISourceModule getSourceModule() { |
| return null; |
| } |
| |
| @Override |
| public void accept(IModelElementVisitor visitor) throws ModelException { |
| if (visitor.visit(this)) { |
| IModelElement[] elements = getChildren(); |
| for (int i = 0; i < elements.length; ++i) { |
| elements[i].accept(visitor); |
| } |
| if (visitor instanceof IModelElementVisitorExtension) { |
| ((IModelElementVisitorExtension) visitor).endVisit(this); |
| } |
| } |
| } |
| } |