/*******************************************************************************
* Copyright (c) 2002, 2013 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)
*******************************************************************************/
package org.eclipse.cdt.internal.core.model;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICElementDelta;
import org.eclipse.cdt.core.model.ICModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IOpenable;
import org.eclipse.cdt.core.model.ISourceRoot;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.model.LanguageManager;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager.ContentTypeChangeEvent;
import org.eclipse.core.runtime.preferences.IScopeContext;
/**
* ContentType processor
*/
public class ContentTypeProcessor extends CModelOperation {
CModelManager fManager;
CElementDelta fCurrentDelta;
ContentTypeChangeEvent[] fEvents;
public ContentTypeProcessor(ContentTypeChangeEvent[] events) {
super(CModelManager.getDefault().getCModel());
this.fEvents = events;
fManager = CModelManager.getDefault();
ICElement root = fManager.getCModel();
fCurrentDelta = new CElementDelta(root);
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
protected void executeOperation() throws CModelException {
for (int i = 0; i < fEvents.length; ++i) {
IContentType contentType = fEvents[i].getContentType();
IScopeContext context = fEvents[i].getContext();
ICProject[] cprojects = getAffectedProjects(fEvents[i]);
for (int k = 0; k < cprojects.length; ++k) {
processContentType(cprojects[k], contentType, context);
}
}
if (fCurrentDelta.getAffectedChildren().length > 0) {
addDelta(fCurrentDelta);
}
}
static public void processContentTypeChanges(ContentTypeChangeEvent[] events) {
try {
CModelOperation op = new ContentTypeProcessor(events);
op.runOperation(null);
} catch (CModelException e) {
//
}
}
private boolean isRegisteredContentTypeId(String id) {
// bailout early
if (id == null || id.length() == 0) {
return false;
}
return LanguageManager.getInstance().isContributedContentType(id);
}
protected void processContentType(ICElement celement, IContentType contentType, IScopeContext context) {
/*
* We only know that an association was added/removed from the contentType.
* we need to go to all the members that are in the cache(known ICElement) and recheck there
* state to see if there were affected. That also include children that were non-celement
* the new assiociation may have turn them to new ICElement.
* algo:
* if ICContainer check if we have a CElementinfo(children)
* if yes,
* look at the members(IResource) and get there contentType
* if the type is the same as the event
* check if element was already a celement
* if yes,
* check if the element as the same contentType
* if yes,
* do nothing
* else
* if the content ID is registered ID
* fired changed event
* else
* fire remove event
* check for the CElement
* get the new oldID and the newID
* if not equal
* check if the newID is registered
* if yes
* fire changed event
* else
* fire remove event
*/
if (celement instanceof IOpenable) {
int type = celement.getElementType();
// if the type is not a TranslationUnit
switch (type) {
case ICElement.C_PROJECT: {
CElementInfo info = (CElementInfo)fManager.peekAtInfo(celement);
if (info != null) {
ICElement[] celements = info.getChildren();
for (int i = 0; i < celements.length; ++i) {
processContentType(celements[i], contentType, context);
}
}
break;
}
case ICElement.C_CCONTAINER: {
CElementInfo info = (CElementInfo)fManager.peekAtInfo(celement);
if (info != null) {
try {
ICElement[] celements = info.getChildren();
IResource resource = celement.getResource();
IResource[] members = null;
if (resource instanceof IContainer) {
members = ((IContainer)resource).members();
}
if (members != null) {
for (int i = 0; i < members.length; ++i) {
if (members[i] instanceof IFile) {
IFile file = (IFile) members[i];
String name = file.getName();
IContentType cType = CCorePlugin.getContentType(file.getProject(), name);
if (cType != null && cType.equals(contentType)) {
boolean found = false;
for (int j = 0; j < celements.length; ++j) {
if (celements[j].getElementName().equals(name)) {
if (celements[j].getElementType() == ICElement.C_UNIT) {
ITranslationUnit unit = (ITranslationUnit)celements[j];
if (!cType.getId().equals(unit.getContentTypeId())) {
if (isRegisteredContentTypeId(cType.getId())) {
elementChanged(celements[j]);
}
}
}
found = true;
break;
}
}
if (! found) {
ICElement newElement = CoreModel.getDefault().create(file);
if (newElement != null) {
elementAdded(newElement, celement);
}
}
}
}
}
}
for (int i = 0; i < celements.length; ++i) {
processContentType(celements[i], contentType, context);
}
} catch (CoreException e) {
//
}
}
}
break;
case ICElement.C_UNIT: {
String oldId = ((ITranslationUnit)celement).getContentTypeId();
if (contentType.getId().equals(oldId)) {
try {
IContentType cType = CCorePlugin.getContentType(celement.getCProject().getProject(), celement.getElementName());
String newId = (cType != null) ? cType.getId() : ""; //$NON-NLS-1$
if (isRegisteredContentTypeId(newId)) {
if (!oldId.equals(newId)) {
elementChanged(celement);
}
} else {
elementRemoved(celement, celement.getParent());
}
} catch (CoreException e) {
//
}
}
break;
}
}
}
}
/**
* Add the resource delta to the right CElementDelta tree.
* @param parent
* @param delta
*/
private void nonCResourcesChanged(ICElement parent) {
if (parent instanceof Openable && ((Openable)parent).isOpen()) {
try {
CElementInfo info = ((Openable)parent).getElementInfo();
switch (parent.getElementType()) {
case ICElement.C_MODEL:
((CModelInfo)info).setNonCResources(null);
break;
case ICElement.C_PROJECT:
((CProjectInfo)info).setNonCResources(null);
break;
case ICElement.C_CCONTAINER:
((CContainerInfo)info).setNonCResources(null);
if (parent instanceof ISourceRoot) {
// if sourceRoot == Project we nee to update the nonCResource of the project also.
if (parent.getResource() instanceof IProject) {
ICElement cproject = parent.getCProject();
CProjectInfo pinfo = (CProjectInfo)fManager.peekAtInfo(cproject);
if (pinfo != null) {
pinfo.setNonCResources(null);
}
}
}
break;
}
} catch (CModelException e) {
//
}
}
}
private void elementAdded(ICElement celement, ICElement parent) throws CModelException {
if (celement instanceof Openable) {
addToParentInfo((Openable)celement);
}
fCurrentDelta.added(celement);
nonCResourcesChanged(parent);
}
/**
* Adds the given child handle to its parent's cache of children.
*/
private 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);
}
}
}
private void elementRemoved(ICElement celement, ICElement parent) throws CModelException {
fCurrentDelta.removed(celement);
nonCResourcesChanged(parent);
removeFromParentInfo(celement);
fManager.releaseCElement(celement);
}
private void elementChanged(ICElement element) throws CModelException {
// For Binary/Archive We can not call close() to do the work
// closing will remove the element from the {Binary,Archive}Container
// We neef to clear the cache explicitely
// if (element instanceof IBinary || element instanceof IArchive) {
// closeBinary(element);
// } else if (element instanceof Openable) {
// close((Openable)element);
// }
// fCurrentDelta.changed(element, ICElementDelta.F_CONTENT);
if (element instanceof IOpenable) {
((IOpenable)element).close();
}
fCurrentDelta.changed(element, ICElementDelta.F_CONTENT |ICElementDelta.F_CONTENT_TYPE);
}
/**
* 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 {
// Remove the child from the parent list.
ICElement parent = child.getParent();
if (parent != null && parent instanceof Parent && fManager.peekAtInfo(parent) != null) {
((Parent)parent).removeChild(child);
}
}
private ICProject[] getAffectedProjects(ContentTypeChangeEvent event) {
try {
ICModel cmodel = CoreModel.getDefault().getCModel();
ICProject[] cprojects = cmodel.getCProjects();
IScopeContext context = event.getContext();
if (context != null) {
if (ProjectScope.SCOPE.equals((context.getName()))) {
IPath location = context.getLocation();
for (int i = 0; i < cprojects.length; i++) {
if (cprojects[i].getProject().getLocation().isPrefixOf(location)) {
return new ICProject[] { cprojects[i] };
}
}
return new ICProject[0];
}
}
// Assume a workspace resolver
List<ICProject> list = new ArrayList<ICProject>(cprojects.length);
for (int i = 0; i < cprojects.length; ++i) {
list.add(cprojects[i]);
}
return list.toArray(new ICProject[list.size()]);
} catch (CModelException e) {
//
}
return new ICProject[0];
}
}