/******************************************************************************* * Copyright (c) 2000, 2014 QNX Software Systems 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: * QNX Software Systems - Initial API and implementation * Markus Schorn (Wind River Systems) * Anton Leherbauer (Wind River Systems) *******************************************************************************/ package org.eclipse.cdt.internal.core.model; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.Map; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ICElementVisitor; import org.eclipse.cdt.core.model.ICModel; import org.eclipse.cdt.core.model.ICModelStatusConstants; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.IOpenable; import org.eclipse.cdt.core.model.IParent; import org.eclipse.cdt.core.model.ISourceRange; import org.eclipse.cdt.core.model.ISourceReference; import org.eclipse.cdt.core.model.ISourceRoot; import org.eclipse.cdt.core.model.IWorkingCopy; import org.eclipse.cdt.internal.core.util.MementoTokenizer; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourceAttributes; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.PlatformObject; public abstract class CElement extends PlatformObject implements ICElement { public static final char CEM_ESCAPE = '\\'; public static final char CEM_CPROJECT = '='; public static final char CEM_SOURCEROOT = '/'; public static final char CEM_SOURCEFOLDER = '<'; public static final char CEM_TRANSLATIONUNIT = '{'; public static final char CEM_SOURCEELEMENT = '['; public static final char CEM_PARAMETER = '('; public static final char CEM_ELEMENTTYPE = '#'; protected static final CElement[] NO_ELEMENTS = {}; protected int fType; protected ICElement fParent; protected String fName; protected CElement(ICElement parent, String name, int type) { fParent= parent; fName= name; fType= type; } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Object getAdapter(Class adapter) { // handle all kinds of resources if (IResource.class.isAssignableFrom(adapter)) { IResource r= getResource(); if (r != null && adapter.isAssignableFrom(r.getClass())) { return r; } } return super.getAdapter(adapter); } // setters public void setElementType (int type) { fType= type; } public void setElementName(String name) { fName = name; } public void setParent (ICElement parent) { fParent = parent; } // getters @Override public int getElementType() { return fType; } @Override public String getElementName() { return fName; } @Override public ICElement getParent() { return fParent; } @Override public IPath getPath() { IResource res = getUnderlyingResource(); if (res != null) return res.getFullPath(); return new Path(getElementName()); } @Override public URI getLocationURI() { IResource res = getUnderlyingResource(); return res == null ? null : res.getLocationURI(); } @Override public boolean exists() { try { return getElementInfo() != null; } catch (CModelException e) { // Do not log it, otherwise it would fill the .log alarming the user. return false; } } /** * Returns the element that is located at the given source offset * in this element. This is a helper method for <code>ITranslationUnit#getElementAtOffset</code>, * and only works on compilation units and types. The offset given is * known to be within this element's source range already, and if no finer * grained element is found at the offset, this element is returned. */ protected ICElement getSourceElementAtOffset(int offset) throws CModelException { if (this instanceof ISourceReference && this instanceof Parent) { ICElement[] children = ((Parent) this).getChildren(); for (ICElement aChild : children) { if (aChild instanceof ISourceReference) { ISourceReference child = (ISourceReference) aChild; ISourceRange range = child.getSourceRange(); int startPos = range.getStartPos(); int endPos = startPos + range.getLength(); if (offset < endPos && offset >= startPos) { if (child instanceof Parent) { return ((Parent) child).getSourceElementAtOffset(offset); } return (ICElement) child; } } } } else { // should not happen //Assert.isTrue(false); } return this; } /** * Returns the elements that are located at the given source offset * in this element. This is a helper method for <code>ITranslationUnit#getElementAtOffset</code>, * and only works on compilation units and types. The offset given is * known to be within this element's source range already, and if no finer * grained element is found at the offset, this element is returned. */ protected ICElement[] getSourceElementsAtOffset(int offset) throws CModelException { if (this instanceof ISourceReference && this instanceof Parent) { ArrayList<Object> list = new ArrayList<Object>(); ICElement[] children = ((Parent) this).getChildren(); for (ICElement aChild : children) { if (aChild instanceof ISourceReference) { ISourceReference child = (ISourceReference) aChild; ISourceRange range = child.getSourceRange(); int startPos = range.getStartPos(); int endPos = startPos + range.getLength(); if (offset < endPos && offset >= startPos) { if (child instanceof Parent) { ICElement[] elements = ((Parent) child).getSourceElementsAtOffset(offset); list.addAll(Arrays.asList(elements)); } list.add(child); } } } children = new ICElement[list.size()]; list.toArray(children); return children; } return new ICElement[]{this}; } @Override public boolean isReadOnly () { IResource r = getUnderlyingResource(); if (r != null) { ResourceAttributes attributes = r.getResourceAttributes(); if (attributes != null) { return attributes.isReadOnly(); } } return false; } @Override public boolean isStructureKnown() throws CModelException { return getElementInfo().isStructureKnown(); } @Override public ICModel getCModel () { ICElement current = this; do { if (current instanceof ICModel) return (ICModel) current; } while ((current = current.getParent()) != null); return null; } @Override public ICProject getCProject() { ICElement current = this; do { if (current instanceof ICProject) return (ICProject) current; } while ((current = current.getParent()) != null); return null; } protected void addChild(ICElement e) throws CModelException { } @Override public IResource getUnderlyingResource() { ICElement current = this; do { IResource res = getResource(); if (res != null) return res; } while ((current = current.getParent()) != null); return null; } @Override public abstract IResource getResource(); protected abstract CElementInfo createElementInfo(); /** * Tests if an element has the same name, type and an equal parent. */ @Override public boolean equals (Object o) { if (this == o) return true; if (o instanceof ICElement) { return equals(this, (ICElement) o); } return false; } public static boolean equals(ICElement lhs, ICElement rhs) { if (lhs == rhs) { return true; } if (lhs.getElementType() != rhs.getElementType()) { return false; } String lhsName= lhs.getElementName(); String rhsName= rhs.getElementName(); if (lhsName == null || rhsName == null || lhsName.length() != rhsName.length() || !lhsName.equals(rhsName)) { return false; } if (lhs instanceof ISourceReference && rhs instanceof ISourceReference) { if (((ISourceReference) lhs).getIndex() != ((ISourceReference) rhs).getIndex()) { return false; } } ICElement lhsParent= lhs.getParent(); ICElement rhsParent= rhs.getParent(); if (lhsParent == rhsParent) { return true; } return lhsParent != null && lhsParent.equals(rhsParent); } public CElementInfo getElementInfo() throws CModelException { return getElementInfo(null); } public CElementInfo getElementInfo (IProgressMonitor monitor) throws CModelException { CModelManager manager = CModelManager.getDefault(); CElementInfo info = (CElementInfo) manager.getInfo(this); if (info != null) { return info; } info = createElementInfo(); openWhenClosed(info, monitor); return info; } @Override public String toString() { return getElementName(); } public String toDebugString() { return getElementName() + " " + getTypeString(); //$NON-NLS-1$ } // util public String getTypeString() { switch (getElementType()) { case C_MODEL: return "CMODEL"; //$NON-NLS-1$ case C_PROJECT: return "CPROJECT"; //$NON-NLS-1$ case C_CCONTAINER: if (this instanceof ISourceRoot) { return "SOURCE_ROOT"; //$NON-NLS-1$ } return "CCONTAINER"; //$NON-NLS-1$ case C_UNIT: if (this instanceof IWorkingCopy) { return "WORKING_UNIT"; //$NON-NLS-1$ } return "TRANSLATION_UNIT"; //$NON-NLS-1$ case C_FUNCTION: return "C_FUNCTION"; //$NON-NLS-1$ case C_FUNCTION_DECLARATION: return "C_FUNCTION_DECLARATION"; //$NON-NLS-1$ case C_VARIABLE: return "C_VARIABLE"; //$NON-NLS-1$ case C_VARIABLE_DECLARATION: return "C_VARIABLE_DECLARATION"; //$NON-NLS-1$ case C_INCLUDE: return "C_INCLUDE"; //$NON-NLS-1$ case C_MACRO: return "C_MACRO"; //$NON-NLS-1$ case C_STRUCT: return "C_STRUCT"; //$NON-NLS-1$ case C_CLASS: return "C_CLASS"; //$NON-NLS-1$ case C_UNION: return "C_UNION"; //$NON-NLS-1$ case C_FIELD: return "C_FIELD"; //$NON-NLS-1$ case C_METHOD: return "C_METHOD"; //$NON-NLS-1$ case C_NAMESPACE: return "C_NAMESPACE"; //$NON-NLS-1$ case C_USING: return "C_USING"; //$NON-NLS-1$ case C_VCONTAINER: return "C_CONTAINER"; //$NON-NLS-1$ case C_BINARY: return "C_BINARY"; //$NON-NLS-1$ case C_ARCHIVE: return "C_ARCHIVE"; //$NON-NLS-1$ default: return "UNKNOWN"; //$NON-NLS-1$ } } /** * Close the C Element * @throws CModelException */ public void close() throws CModelException { CModelManager.getDefault().releaseCElement(this); } /** * This element is being closed. Do any necessary cleanup. */ protected void closing(Object info) throws CModelException { } /** * This element has just been opened. Do any necessary setup. */ protected void opening(Object info) { } /** * 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() { if (fParent instanceof IOpenable) { return (IOpenable) fParent; } return null; } /** * 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 void generateInfos(CElementInfo info, Map<ICElement, CElementInfo> newElements, IProgressMonitor monitor) throws CModelException; /** * Open a <code>IOpenable</code> that is known to be closed (no check for * <code>isOpen()</code>). */ protected void openWhenClosed(CElementInfo info, IProgressMonitor pm) throws CModelException { CModelManager manager = CModelManager.getDefault(); boolean hadTemporaryCache = manager.hasTemporaryCache(); try { Map<ICElement, CElementInfo> newElements = manager.getTemporaryCache(); generateInfos(info, newElements, pm); if (info == null) { info = newElements.get(this); } if (info == null) { // A source ref element could not be opened. // Close any buffer that was opened for the openable parent. Iterator<ICElement> iterator = newElements.keySet().iterator(); while (iterator.hasNext()) { ICElement element = iterator.next(); if (element instanceof Openable) { ((Openable) element).closeBuffer(); } } throw newNotPresentException(); } if (!hadTemporaryCache) { manager.putInfos(this, newElements); } } finally { if (!hadTemporaryCache) { manager.resetTemporaryCache(); } } } /** * @see ICElement */ @Override public ICElement getAncestor(int ancestorType) { ICElement element = this; while (element != null) { if (element.getElementType() == ancestorType) { return element; } element= element.getParent(); } return null; } /** * Returns true if this element is an ancestor of the given element, * otherwise false. */ public boolean isAncestorOf(ICElement e) { ICElement parent= e.getParent(); while (parent != null && !parent.equals(this)) { parent= parent.getParent(); } return parent != null; } /** * Creates and returns and not present exception for this element. */ protected CModelException newNotPresentException() { return new CModelException(new CModelStatus(ICModelStatusConstants.ELEMENT_DOES_NOT_EXIST, this)); } /** * Returns the hash code for this Java 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. */ // CHECKPOINT: making not equal objects seem equal // What elements should override this? @Override public int hashCode() { return hashCode(this); } public static int hashCode(ICElement elem) { ICElement parent= elem.getParent(); if (parent == null) { return System.identityHashCode(elem); } return Util.combineHashCodes(elem.getElementName().hashCode(), parent.hashCode()); } /** * Checks if two objects are identical * Subclasses should override accordingly */ public boolean isIdentical(CElement otherElement){ return this.equals(otherElement); } @Override public void accept(ICElementVisitor visitor) throws CoreException { // Visit me, return right away if the visitor doesn't want to visit my children if (!visitor.visit(this)) return; // If I am a Parent, visit my children if (this instanceof IParent) { ICElement [] children = ((IParent) this).getChildren(); for (int i = 0; i < children.length; ++i) { children[i].accept(visitor); } } } @Override public String getHandleIdentifier() { return getHandleMemento(); } /** * Builds a string representation of this element. * * @return the string representation */ public String getHandleMemento(){ StringBuilder buff = new StringBuilder(); getHandleMemento(buff); return buff.toString(); } /** * Append this elements memento string to the given buffer. * * @param buff the buffer building the memento string */ public void getHandleMemento(StringBuilder buff) { ((CElement) 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(); /** * Creates a C element handle from the given memento. * * @param memento the memento tokenizer */ public ICElement getHandleFromMemento(MementoTokenizer memento) { if (!memento.hasMoreTokens()) return this; String token = memento.nextToken(); return getHandleFromMemento(token, memento); } /** * Creates a C element handle from the given memento. * The given token is the current delimiter indicating the type of the next token(s). * * @param token the curren memento token * @param memento the memento tokenizer */ public abstract ICElement getHandleFromMemento(String token, MementoTokenizer memento); /** * Escape special characters in the given name and append the result to buffer. * * @param buffer the buffer to build the memento string * @param mementoName the name to escape */ public static void escapeMementoName(StringBuilder buffer, String mementoName) { for (int i = 0, length = mementoName.length(); i < length; i++) { char character = mementoName.charAt(i); switch (character) { case CEM_ESCAPE: case CEM_CPROJECT: case CEM_TRANSLATIONUNIT: case CEM_SOURCEROOT: case CEM_SOURCEFOLDER: case CEM_SOURCEELEMENT: case CEM_ELEMENTTYPE: case CEM_PARAMETER: buffer.append(CEM_ESCAPE); } buffer.append(character); } } }