/******************************************************************************* * Copyright (c) 2002, 2011 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: * Rational Software - Initial API and implementation * Anton Leherbauer (Wind River Systems) *******************************************************************************/ package org.eclipse.cdt.internal.core.model; import java.util.Arrays; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.CoreModel; import org.eclipse.cdt.core.model.IArchive; import org.eclipse.cdt.core.model.IArchiveContainer; import org.eclipse.cdt.core.model.IBinary; import org.eclipse.cdt.core.model.IBinaryContainer; import org.eclipse.cdt.core.model.ICContainer; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ICElementDelta; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ISourceRoot; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.runtime.IPath; /** * This class is used by <code>CModelManager</code> to convert * <code>IResourceDelta</code>s into <code>ICElementDelta</code>s. * It also does some processing on the <code>CElement</code>s involved. * (e.g. closing them or updating binary containers). */ final class DeltaProcessor { /** * The <code>CElementDelta</code> corresponding to the <code>IResourceDelta</code> being translated. */ private CElementDelta fCurrentDelta; static final ICElementDelta[] NO_DELTA = new ICElementDelta[0]; // Hold on the element being renamed. private ICElement movedFromElement = null; /** * Creates the create corresponding to this resource. * Returns null if none was found. */ protected ICElement createElement(IResource resource) { if (resource == null) { return null; } CModelManager manager = CModelManager.getDefault(); boolean shouldProcess = true; // Check for C nature or if the was a CNature if (!(resource instanceof IWorkspaceRoot)) { IProject project = resource.getProject(); if (!(CoreModel.hasCNature(project) || CoreModel.hasCCNature(project))) { shouldProcess = false; CModel root = manager.getCModel(); CModelInfo rootInfo = (CModelInfo)manager.peekAtInfo(root); if (rootInfo != null) { ICElement[] celements = rootInfo.getChildren(); for (ICElement celement : celements) { IResource r = celement.getResource(); if (project.equals(r)) { shouldProcess = true; } } } } } if (!shouldProcess) { return null; } ICElement celement = manager.create(resource, null); // BUG 36424: // The Binary may only be visible in the BinaryContainers try { if (celement == null && resource.getType() == IResource.FILE) { ICElement[] children; ICProject cproj = manager.create(resource.getProject()); if (cproj != null && cproj.isOpen()) { IBinaryContainer bin = cproj.getBinaryContainer(); if (bin.isOpen()) { children = ((CElement)bin).getElementInfo().getChildren(); for (ICElement element : children) { IResource res = element.getResource(); if (resource.equals(res)) { celement = element; break; } } } } } // BUG 36424: // The Archive may only be visible in the ArchiveContainers if (celement == null && resource.getType() == IResource.FILE) { ICElement[] children; ICProject cproj = manager.create(resource.getProject()); if (cproj != null && cproj.isOpen()) { IArchiveContainer ar = cproj.getArchiveContainer(); if (ar.isOpen()) { children = ((CElement)ar).getElementInfo().getChildren(); for (ICElement element : children) { IResource res = element.getResource(); if (resource.equals(res)) { celement = element; break; } } } } } // It is not a C resource if the parent is a Binary/ArchiveContainer // But we have to release too. if (celement != null && resource.getType() == IResource.FILE) { ICElement parent = celement.getParent(); if (parent instanceof IArchiveContainer || parent instanceof IBinaryContainer) { releaseCElement(celement); celement = null; } } } catch (CModelException e) { return null; } return celement; } /** * Adds the given child handle to its parent's cache of children. */ protected void addToParentInfo(Openable child) throws CModelException { Openable parent = (Openable) child.getParent(); if (parent != null && parent.isOpen()) { CElementInfo info = parent.getElementInfo(); // Check if the element exits if (!info.includesChild(child)) { info.addChild(child); } } } /** * Removes the given element from its parents cache of children. If the * element does not have a parent, or the parent is not currently open, * this has no effect. */ private void removeFromParentInfo(ICElement child) throws CModelException { CModelManager factory = CModelManager.getDefault(); // Remove the child from the parent list. ICElement parent = child.getParent(); if (parent != null && parent instanceof Parent && factory.peekAtInfo(parent) != null) { ((Parent)parent).removeChild(child); } } /** * Release the Element and cleaning. */ protected void releaseCElement(ICElement celement) throws CModelException { CModelManager factory = CModelManager.getDefault(); int type = celement.getElementType(); if (type == ICElement.C_ARCHIVE) { ICProject cproject = celement.getCProject(); IArchiveContainer container = cproject.getArchiveContainer(); fCurrentDelta.changed(container, ICElementDelta.CHANGED); } else if (type == ICElement.C_BINARY) { ICProject cproject = celement.getCProject(); IBinaryContainer container = cproject.getBinaryContainer(); fCurrentDelta.changed(container, ICElementDelta.CHANGED); } else { // If an entire folder was deleted we need to update the // BinaryContainer/ArchiveContainer also. ICProject cproject = celement.getCProject(); CProjectInfo pinfo = (CProjectInfo)factory.peekAtInfo(cproject); if (pinfo != null && pinfo.vBin != null) { if (factory.peekAtInfo(pinfo.vBin) != null) { ICElement[] bins = pinfo.vBin.getChildren(); for (ICElement bin : bins) { if (celement.getPath().isPrefixOf(bin.getPath())) { fCurrentDelta.changed(pinfo.vBin, ICElementDelta.CHANGED); } } } } if (pinfo != null && pinfo.vLib != null) { if (factory.peekAtInfo(pinfo.vLib) != null) { ICElement[] ars = pinfo.vLib.getChildren(); for (ICElement ar : ars) { if (celement.getPath().isPrefixOf(ar.getPath())) { fCurrentDelta.changed(pinfo.vBin, ICElementDelta.CHANGED); } } } } } removeFromParentInfo(celement); factory.releaseCElement(celement); } /** * Creates the create corresponding to this resource. * Returns null if none was found. */ protected ICElement createElement(IPath path) { return CModelManager.getDefault().create(path); } /** * Processing for an element that has been added:<ul> * <li>If the element is a project, do nothing, and do not process * children, as when a project is created it does not yet have any * natures - specifically a C nature. * <li>If the element is not a project, process it as added (see * <code>basicElementAdded</code>. * </ul> */ protected void elementAdded(ICElement element, IResourceDelta delta) throws CModelException { if (element instanceof Openable) { addToParentInfo((Openable)element); } if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { //ICElement movedFromElement = createElement(delta.getMovedFromPath()); if (movedFromElement == null) { movedFromElement = createElement(delta.getMovedFromPath()); } fCurrentDelta.movedTo(element, movedFromElement); movedFromElement = null; } else { fCurrentDelta.added(element); } } /** * Processing for the closing of an element - there are two cases:<ul> * <li>when a project is closed (in the platform sense), the * CModel reports this as if the CProject has been removed. * <li>otherwise, the CModel reports this * as a the element being closed (CHANGED + F_CLOSED). * </ul> * <p>In both cases, the children of the element are not processed. When * a resource is closed, the platform reports all children as removed. */ protected void elementClosed(ICElement element, IResourceDelta delta) throws CModelException { if (element.getElementType() == ICElement.C_PROJECT) { // treat project closing as removal elementRemoved(element, delta); CModelInfo rootInfo = (CModelInfo)CModelManager.getDefault().getCModel().getElementInfo(); rootInfo.setNonCResources(null); } else { fCurrentDelta.closed(element); } } /** * Processing for the opening of an element - there are two cases:<ul> * <li>when a project is opened (in the platform sense), the * CModel reports this as if the CProject has been added. * <li>otherwise, the CModel reports this * as a the element being opened (CHANGED + F_CLOSED). * </ul> */ protected void elementOpened(ICElement element, IResourceDelta delta) throws CModelException { if (element.getElementType() == ICElement.C_PROJECT) { // treat project opening as addition if (hasCNature(delta.getResource())) { elementAdded(element, delta); } CModelInfo rootInfo = (CModelInfo)CModelManager.getDefault().getCModel().getElementInfo(); rootInfo.setNonCResources(null); } else { fCurrentDelta.opened(element); } } /* * Closes the given element, which removes it from the cache of open elements. */ private void close(Openable element) { try { element.close(); } catch (CModelException e) { // do nothing } } /** * This is use to remove the cache info for IArchive and IBinary * We can use IBinary.close() doing this will remove the binary * for the virtual binary/archive containers. * @param celement */ private void closeBinary(ICElement celement) { CModelManager factory = CModelManager.getDefault(); CElementInfo pinfo = (CElementInfo)factory.peekAtInfo(celement); if (pinfo != null) { ICElement[] celems = pinfo.getChildren(); for (int i = 0; i < celems.length; ++i) { closeBinary(celems[i]); } factory.removeInfo(celement); } } /** * Generic processing for elements with changed contents:<ul> * <li>The element is closed such that any subsequent accesses will re-open * the element reflecting its new structure. * <li>An entry is made in the delta reporting a content change (K_CHANGE with F_CONTENT flag set). * </ul> */ protected void elementChanged(ICElement element, IResourceDelta delta) { // For Binary/Archive We can not call close() to do the work // closing will remove the element from the {Binary,Archive}Container // We need to clear the cache explicitly if (element instanceof IBinary || element instanceof IArchive) { closeBinary(element); } else if (element instanceof Openable) { close((Openable)element); } fCurrentDelta.changed(element, ICElementDelta.F_CONTENT); } /** * Generic processing for a removed element:<ul> * <li>Close the element, removing its structure from the cache * <li>Remove the element from its parent's cache of children * <li>Add a REMOVED entry in the delta * </ul> */ protected void elementRemoved(ICElement element, IResourceDelta delta) throws CModelException { if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { IPath movedToPath = delta.getMovedToPath(); // create the moved to element ICElement movedToElement = createElement(movedToPath); if (movedToElement == null) { // moved outside fCurrentDelta.removed(element); } else { movedFromElement = element; fCurrentDelta.movedFrom(element, movedToElement); } } else { fCurrentDelta.removed(element); } releaseCElement(element); } /** * Filters the generated <code>CElementDelta</code>s to remove those * which should not be fired (because they don't represent a real change * in the C Model). */ protected ICElementDelta[] filterRealDeltas(ICElementDelta[] deltas) { int length = deltas.length; ICElementDelta[] realDeltas = null; int index = 0; for (int i = 0; i < length; i++) { CElementDelta delta = (CElementDelta)deltas[i]; if (delta == null) { continue; } if (delta.getAffectedChildren().length > 0 || delta.getKind() == ICElementDelta.ADDED || delta.getKind() == ICElementDelta.REMOVED || (delta.getFlags() & ICElementDelta.F_CLOSED) != 0 || (delta.getFlags() & ICElementDelta.F_OPENED) != 0 || delta.resourceDeltasCounter > 0) { if (realDeltas == null) { realDeltas = new ICElementDelta[length]; } realDeltas[index++] = delta; } } if (index > 0) { ICElementDelta[] result = new ICElementDelta[index]; System.arraycopy(realDeltas, 0, result, 0, index); return result; } return NO_DELTA; } /** * Returns true if the given resource is contained in an open project * with a C nature, otherwise false. */ protected boolean hasCNature(IResource resource) { // ensure the project has a C nature (if open) IProject project = resource.getProject(); if (project.isOpen()) { return CoreModel.hasCNature(project); } return false; } /** * Converts a <code>IResourceDelta</code> rooted in a <code>Workspace</code> into * the corresponding set of <code>ICElementDelta</code>, rooted in the * relevant <code>CModel</code>s. */ public ICElementDelta[] processResourceDelta(IResourceDelta changes) { ICElement root = CModelManager.getDefault().getCModel(); // get the workspace delta, and start processing there. IResourceDelta[] deltas = changes.getAffectedChildren(); ICElementDelta[] translatedDeltas = new CElementDelta[deltas.length]; //System.out.println("delta.length: " + deltas.length); for (int i = 0; i < deltas.length; i++) { IResourceDelta delta = deltas[i]; fCurrentDelta = new CElementDelta(root); traverseDelta(root, delta); // traverse delta translatedDeltas[i] = fCurrentDelta; } ICElementDelta[] filteredDeltas= filterRealDeltas(translatedDeltas); // release deltas fCurrentDelta= null; return filteredDeltas; } /** * Converts an <code>IResourceDelta</code> and its children into * the corresponding <code>ICElementDelta</code>s. * Return whether the delta corresponds to a C element. * If it is not a C element, it will be added as a non-C * resource by the sender of this method. */ protected void traverseDelta(ICElement parent, IResourceDelta delta) { boolean updateChildren = true; try { IResource resource = delta.getResource(); ICElement current = createElement(resource); updateChildren = updateCurrentDeltaAndIndex(current, delta); if (current == null || current instanceof ICContainer) { if (parent != null) nonCResourcesChanged(parent, delta); } else if (current instanceof ICProject) { ICProject cprj = (ICProject)current; CModel cModel = CModelManager.getDefault().getCModel(); if (!cprj.getProject().isOpen() || cModel.findCProject(cprj.getProject()) == null) { nonCResourcesChanged(parent, delta); } } parent = current; } catch (CModelException e) { } if (updateChildren){ IResourceDelta [] children = delta.getAffectedChildren(); for (IResourceDelta element : children) { traverseDelta(parent, element); } } } /** * Add the resource delta to the right CElementDelta tree. * @param parent * @param delta */ protected void nonCResourcesChanged(ICElement parent, IResourceDelta delta) throws CModelException { if (parent instanceof Openable && ((Openable)parent).isOpen()) { CElementInfo info = ((Openable)parent).getElementInfo(); switch (parent.getElementType()) { case ICElement.C_MODEL: ((CModelInfo)info).setNonCResources(null); fCurrentDelta.addResourceDelta(delta); return; case ICElement.C_PROJECT: { final CProjectInfo pInfo= (CProjectInfo)info; pInfo.setNonCResources(null); ISourceRoot[] roots= pInfo.sourceRoots; if (roots != null) { ICProject cproject = (ICProject)parent; if (isFolderAddition(delta)) { // if source roots changed - refresh from scratch // see http://bugs.eclipse.org/215112 pInfo.sourceRoots= null; ISourceRoot[] newRoots= cproject.getAllSourceRoots(); if (!Arrays.equals(roots, newRoots)) { cproject.close(); break; } } // deal with project == sourceroot. For that case the parent could have been the sourceroot // so we must update the sourceroot nonCResource array also. for (ISourceRoot root : roots) { IResource r = root.getResource(); if (r instanceof IProject) { CElementInfo cinfo = (CElementInfo) CModelManager.getDefault().peekAtInfo(root); if (cinfo instanceof CContainerInfo) { ((CContainerInfo)cinfo).setNonCResources(null); } } } } break; } case ICElement.C_CCONTAINER: ((CContainerInfo)info).setNonCResources(null); break; } } CElementDelta elementDelta = fCurrentDelta.find(parent); if (elementDelta == null) { fCurrentDelta.changed(parent, ICElementDelta.F_CONTENT); elementDelta = fCurrentDelta.find(parent); if (elementDelta != null) { elementDelta.addResourceDelta(delta); } } else { elementDelta.addResourceDelta(delta); } } /** * Test whether this delta or any of its children represents a folder addition. * @param delta * @return <code>true</code>, if the delta contains at least one new folder */ private static boolean isFolderAddition(IResourceDelta delta) { if (delta.getResource().getType() != IResource.FOLDER) return false; if (delta.getKind() == IResourceDelta.ADDED) return true; IResourceDelta[] children= delta.getAffectedChildren(); for (IResourceDelta element : children) { if (isFolderAddition(element)) { return true; } } return false; } /* * Update the current delta (ie. add/remove/change the given element) and update the * corresponding index. * Returns whether the children of the given delta must be processed. * @throws a CModelException if the delta doesn't correspond to a c element of the given type. */ private boolean updateCurrentDeltaAndIndex(ICElement element, IResourceDelta delta) throws CModelException { IResource resource = delta.getResource(); switch (delta.getKind()) { case IResourceDelta.ADDED : if (element != null) { elementAdded(element, delta); if (element instanceof ICContainer) { ICContainer container = (ICContainer) element; ICProject cProject = container.getCProject(); // Always check whether the container is open. if (container.isOpen()) return true; // Check binary container, if the new folder is on an output entry, // there may be new binaries to add if (cProject.isOnOutputEntry(resource)) { IBinaryContainer bin = cProject.getBinaryContainer(); IArchiveContainer archive = cProject.getArchiveContainer(); return bin.isOpen() || archive.isOpen(); } return false; } else if (element instanceof ICProject) { return ((ICProject) element).isOpen(); } else if (element instanceof IBinary) { if (((IBinary) element).showInBinaryContainer()) { ICProject cProject = element.getCProject(); IBinaryContainer bin = cProject.getBinaryContainer(); fCurrentDelta.changed(bin, ICElementDelta.F_CONTENT); } } else if (element instanceof IArchive) { ICProject cProject = element.getCProject(); IArchiveContainer archive = cProject.getArchiveContainer(); fCurrentDelta.changed(archive, ICElementDelta.F_CONTENT); } } return false; case IResourceDelta.REMOVED : if (element != null) { elementRemoved(element, delta); } return element instanceof ICContainer; case IResourceDelta.CHANGED : int flags = delta.getFlags(); if ((flags & IResourceDelta.CONTENT) != 0) { // content has changed if (element != null) { elementChanged(element, delta); } } else if (resource.getType() == IResource.PROJECT) { if ((flags & IResourceDelta.OPEN) != 0) { // project has been opened or closed IProject project = (IProject)resource; if (element != null) { if (project.isOpen()) { elementOpened(element, delta); return element instanceof ICProject && ((ICProject) element).isOpen(); } elementClosed(element, delta); //Don't process children return false; } return false; } if ((flags & IResourceDelta.DESCRIPTION) != 0) { IProject res = (IProject)delta.getResource(); CModel cModel = CModelManager.getDefault().getCModel(); boolean wasCProject = cModel.findCProject(res) != null; boolean isCProject = CProject.hasCNature(res); if (wasCProject != isCProject) { // project's nature has been added or removed if (element != null) { // note its resources are still visible as roots to other projects if (isCProject) { elementOpened(element, delta); } else { elementRemoved(element, delta); } return true; } } } } else if ((flags & IResourceDelta.ENCODING) != 0) { if (element != null) { elementChanged(element, delta); } } return true; } return true; } }