blob: 0e9094e9f0f7ae3d93cba9aba0dca8710f87f6cc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2011 Intel 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:
* Intel Corporation - Initial API and implementation
* Anton Leherbauer (Wind River Systems)
* James Blackburn (Broadcom Corp.)
*******************************************************************************/
package org.eclipse.cdt.internal.core;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CDescriptorEvent;
import org.eclipse.cdt.core.ICDescriptor;
import org.eclipse.cdt.core.ICDescriptorListener;
import org.eclipse.cdt.core.ICDescriptorManager;
import org.eclipse.cdt.core.ICDescriptorOperation;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICDescriptionDelta;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
import org.eclipse.cdt.core.settings.model.ICSettingObject;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationData;
import org.eclipse.cdt.internal.core.settings.model.CConfigurationDescriptionCache;
import org.eclipse.cdt.internal.core.settings.model.CConfigurationSpecSettings;
import org.eclipse.cdt.internal.core.settings.model.CProjectDescription;
import org.eclipse.cdt.internal.core.settings.model.CProjectDescriptionManager;
import org.eclipse.cdt.internal.core.settings.model.ExceptionFactory;
import org.eclipse.cdt.internal.core.settings.model.IInternalCCfgInfo;
import org.eclipse.cdt.internal.core.settings.model.PathEntryConfigurationDataProvider;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
/**
* CConfigBasedDescriptorManager
*
* ICDescriptor settings are set directly within the project description.
*
* The ICDescriptorManager can be used to fetch the current ICDescriptor
* for the project get and set settings in this module in a safe manner.
*
* This thread delegates operations to the particular CConfigBasedDescriptor
* being operated upon. Locking is performed per descriptor. This manager
* provides additional concurrency above any beyond that provided by
* {@link CoreModel#getProjectDescription(IProject)} as each project only
* has one ICDescriptor live at any time. This prevents concurrent modifications
* from overwriting changes made in other threads.
*
* Usage:
* Users should consider making changes to project ICDescriptors using an {@link ICDescriptorOperation}
* with the {@link #runDescriptorOperation} method.
* The ICDescriptor's returned for {@link #getDescriptor} are shared between multiple threads,
* but they are synchronized. This is safe as long as structural changes aren't made to the same
* project storage element from multiple threads.
*
* @see ICDescriptor for more
*/
final public class CConfigBasedDescriptorManager implements ICDescriptorManager {
private volatile static CConfigBasedDescriptorManager fInstance;
public static final String NULL_OWNER_ID = ""; //$NON-NLS-1$
private static volatile Map<String, COwnerConfiguration> fOwnerConfigMap;
private volatile static ICProjectDescriptionListener fDescriptionListener;
private Collection<ICDescriptorListener> fListeners = new CopyOnWriteArraySet<ICDescriptorListener>();
/** Map: IProjet -> CConfigBasedDescriptor weak reference <br />
* Multiple threads operating concurrently will get the same shared
* ICDescriptor, however we don't keep a reference to this for longer
* than is necessary.*/
final ConcurrentHashMap<IProject, Reference<CConfigBasedDescriptor>> fProjectDescriptorMap = new ConcurrentHashMap<IProject, Reference<CConfigBasedDescriptor>>();
private CConfigBasedDescriptorManager(){}
public static CConfigBasedDescriptorManager getInstance(){
if(fInstance == null){
synchronized (CConfigBasedDescriptor.class) {
if(fInstance == null){
fInstance = new CConfigBasedDescriptorManager();
}
}
}
return fInstance;
}
private static final COwnerConfiguration NULLCOwner = new COwnerConfiguration(NULL_OWNER_ID,
CCorePlugin.getResourceString("CDescriptorManager.internal_owner")); //$NON-NLS-1$
/**
* Callback indicating that a project has moved
* @param fromProject
* @param toProject
*/
public void projectMove(IProject fromProject, IProject toProject) {
Reference<CConfigBasedDescriptor> ref = fProjectDescriptorMap.get(fromProject);
if (ref != null)
fProjectDescriptorMap.putIfAbsent(toProject, ref);
fProjectDescriptorMap.remove(fromProject);
}
/**
* Callback to remove references the ICDescriptor when the project
* is closed or removed
* @param project
*/
public void projectClosedRemove(IProject project) {
fProjectDescriptorMap.remove(project);
}
/*
* (non-Javadoc)
* @see org.eclipse.cdt.core.ICDescriptorManager#configure(org.eclipse.core.resources.IProject, java.lang.String)
*/
@Override
public void configure(IProject project, String id) throws CoreException {
if (id.equals(NULLCOwner.getOwnerID())) {
IStatus status = new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1,
CCorePlugin.getResourceString("CDescriptorManager.exception.invalid_ownerID"), //$NON-NLS-1$
(Throwable)null);
throw new CoreException(status);
}
CConfigBasedDescriptor dr = null;
// Only allow one IProject configure
synchronized (this) {
try {
dr = findDescriptor(project, false);
if (dr != null) {
dr.fLock.acquire();
if (dr.getProjectOwner().getID().equals(NULLCOwner.getOwnerID())) {
// non owned descriptors are simply configure to the new owner no questions ask!
dr = updateDescriptor(project, dr, id);
} else if (!dr.getProjectOwner().getID().equals(id)) {
IStatus status = new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, CCorePlugin.STATUS_CDTPROJECT_EXISTS,
CCorePlugin.getResourceString("CDescriptorManager.exception.alreadyConfigured"), //$NON-NLS-1$
(Throwable)null);
throw new CoreException(status);
} else {
return; // already configured with same owner.
}
} else {
dr = findDescriptor(project, true);
dr.fLock.acquire();
dr = updateDescriptor(project, dr, id);
}
dr.apply(true);
if(dr.isOperationStarted())
dr.setOpEvent(new CDescriptorEvent(dr, CDescriptorEvent.CDTPROJECT_ADDED, 0));
} finally {
if (dr != null)
dr.fLock.release();
}
}
}
/**
* Update the descriptor to the particular ownerId
*
* FIXME JBB don't twiddle CConfigBasedDescription state here.
* Creates the project description if non found. Locks the CConfigBasedDescriptor while this takes place
* @param project
* @param dr
* @param ownerId
* @return
* @throws CoreException
*/
private CConfigBasedDescriptor updateDescriptor(IProject project, CConfigBasedDescriptor dr, String ownerId) throws CoreException {
try {
dr.fLock.acquire();
ICConfigurationDescription cfgDes = dr.getConfigurationDescription();
CConfigurationSpecSettings settings = ((IInternalCCfgInfo)cfgDes).getSpecSettings();
settings.setCOwner(ownerId);
COwner owner = settings.getCOwner();
dr = findDescriptor(project, true);
owner.configure(project, dr);
return dr;
} finally {
dr.fLock.release();
}
}
/*
* (non-Javadoc)
* @see org.eclipse.cdt.core.ICDescriptorManager#convert(org.eclipse.core.resources.IProject, java.lang.String)
*/
@Override
public void convert(IProject project, String id) throws CoreException {
CConfigBasedDescriptor dr = findDescriptor(project, false);
if(dr == null)
throw ExceptionFactory.createCoreException(CCorePlugin.getResourceString("CConfigBasedDescriptorManager.0")); //$NON-NLS-1$
try {
dr.fLock.acquire();
dr = updateDescriptor(project, dr, id);
dr.apply(true);
if(dr.isOperationStarted())
dr.setOpEvent(new CDescriptorEvent(dr, CDescriptorEvent.CDTPROJECT_CHANGED, CDescriptorEvent.OWNER_CHANGED));
} finally {
dr.fLock.release();
}
}
/*
* (non-Javadoc)
* @see org.eclipse.cdt.core.ICDescriptorManager#getDescriptor(org.eclipse.core.resources.IProject)
*/
@Override
public ICDescriptor getDescriptor(IProject project) throws CoreException {
return getDescriptor(project, true);
}
/*
* (non-Javadoc)
* @see org.eclipse.cdt.core.ICDescriptorManager#getDescriptor(org.eclipse.core.resources.IProject, boolean)
*/
@Override
public ICDescriptor getDescriptor(IProject project, boolean create) throws CoreException {
return findDescriptor(project, create);
}
/*
* (non-Javadoc)
* @see org.eclipse.cdt.core.ICDescriptorManager#addDescriptorListener(org.eclipse.cdt.core.ICDescriptorListener)
*/
@Override
public void addDescriptorListener(ICDescriptorListener listener) {
fListeners.add(listener);
}
/*
* (non-Javadoc)
* @see org.eclipse.cdt.core.ICDescriptorManager#removeDescriptorListener(org.eclipse.cdt.core.ICDescriptorListener)
*/
@Override
public void removeDescriptorListener(ICDescriptorListener listener) {
fListeners.remove(listener);
}
/*
* Run the descriptor operation. Lock the descriptor while this takes place...
* (non-Javadoc)
* @see org.eclipse.cdt.core.ICDescriptorManager#runDescriptorOperation(org.eclipse.core.resources.IProject, org.eclipse.cdt.core.ICDescriptorOperation, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void runDescriptorOperation(IProject project, ICDescriptorOperation op, IProgressMonitor monitor)
throws CoreException {
CConfigBasedDescriptor dr = findDescriptor(project, true);
if (dr == null)
throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, "Failed to create descriptor", null)); //$NON-NLS-1$
try {
dr.fLock.acquire();
CDescriptorEvent event = null;
try {
dr.operationStart();
op.execute(dr, monitor);
} finally {
event = dr.operationStop();
}
dr.apply(false);
if(event != null)
CConfigBasedDescriptorManager.getInstance().notifyListeners(event);
} finally {
dr.fLock.release();
}
}
/*
* Runs a descriptor operation directly on an ICProjectDescription.
*
* (non-Javadoc)
* @see org.eclipse.cdt.core.ICDescriptorManager#runDescriptorOperation(org.eclipse.core.resources.IProject, org.eclipse.cdt.core.settings.model.ICProjectDescription, org.eclipse.cdt.core.ICDescriptorOperation, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void runDescriptorOperation(IProject project, ICProjectDescription des, ICDescriptorOperation op, IProgressMonitor monitor)
throws CoreException {
// Ensure that only one of these is running on the project at any one time...
if(des.isReadOnly())
throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, CCorePlugin.getResourceString("CConfigBasedDescriptorManager.2"), null)); //$NON-NLS-1$
//create a new descriptor
CConfigBasedDescriptor dr = loadDescriptor((CProjectDescription)des);
if (dr == null)
throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, CCorePlugin.getResourceString("CConfigBasedDescriptorManager.3"), null)); //$NON-NLS-1$
op.execute(dr, monitor);
// reconcile the changes into the passed in ICProjectDescription
CConfigBasedDescriptor.reconcile(dr, des);
}
/**
* Fetch the ICDescriptor for the project.
*
* If there is no project description and create is specified, then a project
* description is created and a descriptor is returned.
*
* Otherwise a share ICDescriptor is returned for the given project
*
* @param project
* @param create create project description if existing description not found
* @throws CoreException
*/
private CConfigBasedDescriptor findDescriptor(IProject project, boolean create) throws CoreException {
if (!project.isAccessible() && !create)
return null;
CConfigBasedDescriptor dr = null;
Reference<CConfigBasedDescriptor> ref = fProjectDescriptorMap.get(project);
if (ref != null)
dr = ref.get();
if (dr != null)
return dr;
// Only create one descriptor at a time...
try {
// Use workspace root lock rule here as:
// CoreException in getProjectDescription can lead to a refresh holding a resource lock.
// Meanwhile the lock might be held by a resource notification thread.
// FIXME JBB we really want to hold a resource lock here...
// However doing so changes the way ProjectDescriptions are created and makes CDescriptor tests fail
// Job.getJobManager().beginRule(ResourcesPlugin.getWorkspace().getRoot(), new NullProgressMonitor());
// if (fProjectDescriptorMap.get(project) != null && (dr = fProjectDescriptorMap.get(project).get()) != null)
// return dr;
// None found, create a new one based off of read-only project description
CProjectDescription des = (CProjectDescription)CProjectDescriptionManager.getInstance().getProjectDescription(project, false);
if(des == null && create)
des = createProjDescriptionForDescriptor(project);
if(des != null)
dr = loadDescriptor(des);
// Use the ConcurrentHashMap to ensure that only one descriptor is live at a time (for a given project...)
ref = fProjectDescriptorMap.putIfAbsent(project, new SoftReference<CConfigBasedDescriptor>(dr));
if (ref != null) {
// Someone was here before us...
CConfigBasedDescriptor dr1 = ref.get();
if (dr1 != null)
return dr1;
synchronized (this) {
ref = fProjectDescriptorMap.putIfAbsent(project, new SoftReference<CConfigBasedDescriptor>(dr));
if (ref != null) {
// Someone was here before us...
dr1 = ref.get();
if (dr1 != null)
return dr1;
}
fProjectDescriptorMap.put(project, new SoftReference<CConfigBasedDescriptor>(dr));
}
}
} finally {
// Job.getJobManager().endRule(ResourcesPlugin.getWorkspace().getRoot());
}
return dr;
}
private static CProjectDescription createProjDescriptionForDescriptor(final IProject project) throws CoreException{
final CProjectDescriptionManager mngr = CProjectDescriptionManager.getInstance();
final CProjectDescription des = (CProjectDescription)mngr.createProjectDescription(project, false, true);
CConfigurationData data = mngr.createDefaultConfigData(project, PathEntryConfigurationDataProvider.getDataFactory());
des.createConfiguration(CCorePlugin.DEFAULT_PROVIDER_ID, data);
return des;
}
/**
* Creates a new CConfigBasedDescriptor from the passed CProjectDescription
*
* static method does not alter any instance state
*
* @param des
* @return CConfigBasedDescriptor or null
* @throws CoreException
*/
private static CConfigBasedDescriptor loadDescriptor(CProjectDescription des) throws CoreException {
if (des == null)
throw ExceptionFactory.createCoreException("CProjectDescription des is null"); //$NON-NLS-1$
if(des.isReadOnly())
des = (CProjectDescription)CProjectDescriptionManager.getInstance().getProjectDescription(des.getProject(), true);
ICConfigurationDescription cfgDes = des.getDefaultSettingConfiguration();
if (cfgDes instanceof CConfigurationDescriptionCache) {
des = (CProjectDescription)CProjectDescriptionManager.getInstance().getProjectDescription(des.getProject(), true);
cfgDes = des.getDefaultSettingConfiguration();
}
if (cfgDes != null){
if(cfgDes.isReadOnly())
throw ExceptionFactory.createCoreException(CCorePlugin.getResourceString("CConfigBasedDescriptorManager.4")); //$NON-NLS-1$
return new CConfigBasedDescriptor(cfgDes);
} else if (!des.isCdtProjectCreating()){
throw ExceptionFactory.createCoreException(CCorePlugin.getResourceString("CConfigBasedDescriptorManager.5")); //$NON-NLS-1$
}
return null;
}
public static synchronized COwnerConfiguration getOwnerConfiguration(String id) {
if (id.equals(NULLCOwner.getOwnerID()))
return NULLCOwner;
if (fOwnerConfigMap == null)
initializeOwnerConfiguration();
COwnerConfiguration config = fOwnerConfigMap.get(id);
if (config == null) { // no install owner, lets create place holder config for it.
config = new COwnerConfiguration(id, CCorePlugin.getResourceString("CDescriptorManager.owner_not_Installed")); //$NON-NLS-1$
fOwnerConfigMap.put(id, config);
}
return config;
}
private static void initializeOwnerConfiguration() {
IExtensionPoint extpoint = Platform.getExtensionRegistry().getExtensionPoint(CCorePlugin.PLUGIN_ID, "CProject"); //$NON-NLS-1$
IExtension extension[] = extpoint.getExtensions();
fOwnerConfigMap = new HashMap<String, COwnerConfiguration>(extension.length);
for (int i = 0; i < extension.length; i++) {
IConfigurationElement element[] = extension[i].getConfigurationElements();
for (int j = 0; j < element.length; j++) {
if (element[j].getName().equalsIgnoreCase("cproject")) { //$NON-NLS-1$
fOwnerConfigMap.put(extension[i].getUniqueIdentifier(), new COwnerConfiguration(element[j]));
break;
}
}
}
}
public void startup(){
if (fDescriptionListener != null)
return;
fDescriptionListener = new ICProjectDescriptionListener(){
@Override
public void handleEvent(CProjectDescriptionEvent event) {
doHandleEvent(event);
}
};
CProjectDescriptionManager.getInstance().addCProjectDescriptionListener(fDescriptionListener,
CProjectDescriptionEvent.APPLIED |
CProjectDescriptionEvent.LOADED |
CProjectDescriptionEvent.DATA_APPLIED |
CProjectDescriptionEvent.ABOUT_TO_APPLY);
}
public void shutdown(){
if(fDescriptionListener != null)
CProjectDescriptionManager.getInstance().removeCProjectDescriptionListener(fDescriptionListener);
fDescriptionListener = null;
}
/**
* Hand CProjectDescription events
* @param event
*/
private void doHandleEvent(CProjectDescriptionEvent event){
CConfigBasedDescriptor dr = null;
// Check for an in memory descriptor matching the current event's project
Reference<CConfigBasedDescriptor> ref = fProjectDescriptorMap.get(event.getProject());
if (ref != null)
dr = ref.get();
// If no delta, return
if (dr == null)
return;
try {
dr.fLock.acquire();
try {
switch(event.getEventType()){
case CProjectDescriptionEvent.LOADED:{
// the descriptor was requested while load process
CProjectDescription des = (CProjectDescription)CProjectDescriptionManager.getInstance().getProjectDescription(event.getProject(), true);
if(des != null){
ICConfigurationDescription cfgDescription = des.getDefaultSettingConfiguration();
if(cfgDescription != null){
dr.updateConfiguration(cfgDescription);
dr.setDirty(false);
}
}
}
break;
case CProjectDescriptionEvent.ABOUT_TO_APPLY:{
CProjectDescription des = (CProjectDescription)event.getNewCProjectDescription();
if(des != null
&& dr.getConfigurationDescription().getProjectDescription() != des){
CConfigBasedDescriptor.reconcile(dr, des);
}
}
break;
case CProjectDescriptionEvent.DATA_APPLIED:{
CProjectDescription des = (CProjectDescription)event.getNewCProjectDescription();
if(des != null){
CConfigBasedDescriptor.reconcile(dr, des);
}
}
break;
case CProjectDescriptionEvent.APPLIED:
CProjectDescription newDes = (CProjectDescription)event.getNewCProjectDescription();
CProjectDescription oldDes = (CProjectDescription)event.getOldCProjectDescription();
CDescriptorEvent desEvent = null;
ICConfigurationDescription updatedCfg = null;
if(oldDes == null){
updatedCfg = newDes.getDefaultSettingConfiguration();
desEvent = new CDescriptorEvent(dr, CDescriptorEvent.CDTPROJECT_ADDED, 0);
} else if(newDes == null) {
desEvent = new CDescriptorEvent(dr, CDescriptorEvent.CDTPROJECT_REMOVED, 0);
} else {
updatedCfg = newDes.getDefaultSettingConfiguration();
ICConfigurationDescription newCfg = newDes.getDefaultSettingConfiguration();
ICConfigurationDescription oldCfg = oldDes.getDefaultSettingConfiguration();
int flags = 0;
if(oldCfg != null && newCfg != null){
if(newCfg.getId().equals(oldCfg.getId())){
ICDescriptionDelta cfgDelta = findCfgDelta(event.getProjectDelta(), newCfg.getId());
if(cfgDelta != null){
flags = cfgDelta.getChangeFlags() & (ICDescriptionDelta.EXT_REF | ICDescriptionDelta.OWNER);
}
} else {
flags = CProjectDescriptionManager.getInstance().calculateDescriptorFlags(newCfg, oldCfg);
}
}
int drEventFlags = descriptionFlagsToDescriptorFlags(flags);
// if(drEventFlags != 0){
desEvent = new CDescriptorEvent(dr, CDescriptorEvent.CDTPROJECT_CHANGED, drEventFlags);
// }
}
if(updatedCfg != null){
CProjectDescription writableDes = (CProjectDescription)CProjectDescriptionManager.getInstance().getProjectDescription(event.getProject(), true);
ICConfigurationDescription indexCfg = writableDes.getDefaultSettingConfiguration();
dr.updateConfiguration(indexCfg);
dr.setDirty(false);
}
notifyListeners(desEvent);
break;
}
} catch (CoreException e){
CCorePlugin.log(e);
}
} finally {
dr.fLock.release();
}
}
private int descriptionFlagsToDescriptorFlags(int flags){
int result = 0;
if((flags & ICDescriptionDelta.EXT_REF) != 0){
result |= CDescriptorEvent.EXTENSION_CHANGED;
}
if((flags & ICDescriptionDelta.OWNER) != 0){
result |= CDescriptorEvent.OWNER_CHANGED;
}
return result;
}
private ICDescriptionDelta findCfgDelta(ICDescriptionDelta delta, String id){
if(delta == null)
return null;
ICDescriptionDelta children[] = delta.getChildren();
for(int i = 0; i < children.length; i++){
ICSettingObject s = children[i].getNewSetting();
if(s != null && id.equals(s.getId()))
return children[i];
}
return null;
}
protected void notifyListeners(final CDescriptorEvent event) {
for (final ICDescriptorListener listener : fListeners) {
SafeRunner.run(new ISafeRunnable() {
@Override
public void handleException(Throwable exception) {
IStatus status = new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1,
CCorePlugin.getResourceString("CDescriptorManager.exception.listenerError"), exception); //$NON-NLS-1$
CCorePlugin.log(status);
}
@Override
public void run() throws Exception {
listener.descriptorChanged(event);
}
});
}
}
}