/*******************************************************************************
* Copyright (c) 2008, 2009 Broadcom 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:
* James Blackburn (Broadcom Corp.)
* IBM Corporation
*******************************************************************************/
package org.eclipse.cdt.internal.core.settings.model;
import com.ibm.icu.text.MessageFormat;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
import org.eclipse.cdt.core.settings.model.ICDescriptionDelta;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionManager;
import org.eclipse.cdt.core.settings.model.ICSettingsStorage;
import org.eclipse.cdt.core.settings.model.ICStorageElement;
import org.eclipse.cdt.internal.core.model.CModelManager;
import org.eclipse.cdt.internal.core.settings.model.ICProjectDescriptionStorageType.CProjectDescriptionStorageTypeProxy;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.osgi.framework.Version;
/**
* This abstract class provides an extension point for functionality for loading
* a CDT Project Description from some kind of backing store. This allows
* extenders to provide their own backing store for a CDT project description.
*
* This class provides the ICProjectDescription root of the project configuration tree in
* which is contained storage modules and other members of the storage element tree.
*
* It is the responsibility of the storage that access to the project storage are threadsafe
* i.e. return writable descriptions aren't shared between multiple threads (or if they
* are, they are suitable synchronized) and setProjectDescription must be aware that
* getProjectDescription may also be called concurrently
*
* <p>
* <strong>EXPERIMENTAL</strong>. This class or interface has been added as
* part of a work in progress. There is no guarantee that this API will work or
* that it will remain the same. Please do not use this API without consulting
* with the CDT team.
* </p>
*
* @since 6.0
*/
public abstract class AbstractCProjectDescriptionStorage {
/** The {@link ICProjectDescriptionStorageType} extension parent of this */
public final CProjectDescriptionStorageTypeProxy type;
/** The version of the project description storage that was loaded */
public final Version version;
/** The project this project-storage is responsible for */
protected volatile IProject project;
/** Flag used to detect if setProjectDescription(...) is called by the thread already in a setProjectDescription(...) */
final private ThreadLocal<Boolean> setProjectDescriptionOperationRunning = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return false;
}
};
/** Before the description is fully applied / loaded, consumers of CProjectDescriptionEvent and CProjectDescription.applyDatas()
* assume that getProjectDescription(...) will return the writable project description in the process of being created / modified...
* Cached temporarily in a thread local variable for this very purpose. */
final private ThreadLocal<ICProjectDescription> currentThreadProjectDescription = new ThreadLocal<ICProjectDescription>();
/**
* @param type CProjectDescriptionStorageTypeProxy
* @param project IProject
* @param version Version
*/
public AbstractCProjectDescriptionStorage(CProjectDescriptionStorageTypeProxy type, IProject project, Version version) {
this.type = type;
this.project = project;
this.version = version;
}
/**
* Returns the project associated with this storage
* @return the IProject associated with the current project
*/
public final IProject getProject() {
return project;
}
/**
* Called in response to a project move event
* @param newProject
* @param oldProject
*/
public void handleProjectMove(IProject newProject, IProject oldProject) {
project = newProject;
}
/**
* Return an ICSettingsStorage root for the given ICStorageElement
* @param element
* @return ICSettingsStorage based off of ICStorageElement
*/
public abstract ICSettingsStorage getStorageForElement(ICStorageElement element) throws CoreException;
/*
* T O C L E A N U P
*/
/*
* FIXME REMOVE
*
* We shouldn't have this in this interface, but SetCProjectDescription creates
* a new description based on an existing description for the delta event. It reconciles
* them later. But this will be non-optimal for most backends
*
* Returns a 'writable' ICStorageElement tree clone of el
*/
public ICStorageElement copyElement(ICStorageElement el, boolean writable) throws CoreException {
return null;
}
/*
* get/setProjectDescription methods
*/
/**
* Return the ICProjectDescription for the specified project.
*
* Use the ICProjectDescriptionManager flags to control the project
* description to be returned
*
* Implementors should call super.getProjectDescription(...) first
* so that current 'threadLocal' project description is returned
*
* @param flags Or of {@link ICProjectDescriptionManager} flags
* @param monitor
* @return an ICProjectDescription corresponding to the given
* @see ICProjectDescriptionManager#GET_WRITABLE
* @see ICProjectDescriptionManager#GET_IF_LOADDED
* @see ICProjectDescriptionManager#GET_EMPTY_PROJECT_DESCRIPTION
* @see ICProjectDescriptionManager#GET_CREATE_DESCRIPTION
* @see ICProjectDescriptionManager#PROJECT_CREATING
*/
public ICProjectDescription getProjectDescription(int flags, IProgressMonitor monitor) throws CoreException {
if (!project.isAccessible())
throw ExceptionFactory.createCoreException(MessageFormat.format(CCorePlugin.getResourceString("ProjectDescription.ProjectNotAccessible"), new Object[] {getProject().getName()})); //$NON-NLS-1$
return currentThreadProjectDescription.get();
}
/**
* The method called by the CProjectDescriptionManager for serializing the project settings
* @param description the project description being set
* @param flags
* @param monitor
* @throws CoreException
*/
public void setProjectDescription(final ICProjectDescription description, final int flags, IProgressMonitor monitor) throws CoreException {
try {
if (monitor == null)
monitor = new NullProgressMonitor();
ICProject cproject = CModelManager.getDefault().create(project);
// The CProjectDescriptionOperation fires the appropriate CElementDeltas calling the callbacks
// below for actual project serialization
SetCProjectDescriptionOperation op = new SetCProjectDescriptionOperation(this, cproject, (CProjectDescription)description, flags);
// Safety: Verify that the listeners of the event call-backs don't recursively call setProjectDescription(...)
// While in the past this recursion hasn't been infinite, the behaviour is 'undefined'.
if (setProjectDescriptionOperationRunning.get()) {
CCorePlugin.log("API Error: setProjectDescription() shouldn't be called recursively.", new Exception()); //$NON-NLS-1$
Job j = new Job("setProjectDescription rescheduled") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
setProjectDescription(description, flags, monitor);
} catch (CoreException e) {
CCorePlugin.log(e);
}
return Status.OK_STATUS;
}
};
j.setSystem(true);
j.schedule();
return;
}
try {
setProjectDescriptionOperationRunning.set(true);
op.runOperation(monitor);
} catch (IllegalArgumentException e) {
throw ExceptionFactory.createCoreException(e);
} finally {
setProjectDescriptionOperationRunning.set(false);
}
} finally {
monitor.done();
}
}
/*
* C A L L B A C K S
* Callbacks for the SetCProjectDescriptionOperation to allow AbstractCProjectDescriptionStorage overrides
*
*/
/**
* Callback
*
* - Actually set the current in memory ICProjectDescription to this
* - If this requires modification of workspace resources then don't serialize
*
* @param des
* @param overwriteIfExists
* @return boolean indicating whether existing read-only project description should be replaced
*/
public abstract boolean setCurrentDescription(ICProjectDescription des, boolean overwriteIfExists);
/**
* Callback
* - Return an IWorkspaceRunnable which will actually perform the serialization of the
* current project description or null if not required
* @return IWorkspaceRunnable that will perform the serialization
*/
public abstract IWorkspaceRunnable createDesSerializationRunnable() throws CoreException;
/*
* R E S O U R C E C H A N G E E V E N T S
*/
/**
* Event fired as a result of a project being moved
*/
public void projectMove(IProject newProject) {
project = newProject;
}
/**
* Event fired as a result of the project being closed or removed
* to allow cleanup of state
*/
public void projectCloseRemove() {
// NOP
}
/*
* C P R O J E C T D E L T A E V E N T S F I R E D
* Protected methods for notifying project description listeners of changes to the proj desc
*/
public static final void fireLoadedEvent(ICProjectDescription desc) {
CProjectDescriptionManager.getInstance().notifyListeners(new CProjectDescriptionEvent(CProjectDescriptionEvent.LOADED, null, desc, null, null));
}
/**
* Fire an event stating that a copy of a description has been created
*
* This is fired when:
* - New writable description is created from read-only store
* @param newDes The new description copy
* @param oldDes The old description
*/
public static final void fireCopyCreatedEvent(ICProjectDescription newDes, ICProjectDescription oldDes) {
CProjectDescriptionManager.getInstance().notifyListeners(new CProjectDescriptionEvent(CProjectDescriptionEvent.COPY_CREATED, null, newDes, oldDes, null));
}
public static final void fireAboutToApplyEvent(ICProjectDescription newDes, ICProjectDescription oldDes) {
CProjectDescriptionManager.getInstance().notifyListeners(new CProjectDescriptionEvent(CProjectDescriptionEvent.ABOUT_TO_APPLY, null, newDes, oldDes, null));
}
public static final CProjectDescriptionEvent createAppliedEvent(ICProjectDescription newDes, ICProjectDescription oldDes, ICProjectDescription appliedDes, ICDescriptionDelta delta) {
return new CProjectDescriptionEvent(CProjectDescriptionEvent.APPLIED, delta, newDes, oldDes, appliedDes);
}
public static final void fireAppliedEvent(ICProjectDescription newDes, ICProjectDescription oldDes, ICProjectDescription appliedDes, ICDescriptionDelta delta) {
CProjectDescriptionManager.getInstance().notifyListeners(createAppliedEvent(newDes, oldDes, appliedDes, delta));
}
/**
* @param newDes - a *writeable* description
* @param oldDes
* @param appliedDes - the description being applied
* @param delta
*/
public static final void fireDataAppliedEvent(ICProjectDescription newDes, ICProjectDescription oldDes, ICProjectDescription appliedDes, ICDescriptionDelta delta) {
CProjectDescriptionManager.getInstance().notifyListeners(new CProjectDescriptionEvent(CProjectDescriptionEvent.DATA_APPLIED, delta, newDes, oldDes, appliedDes));
}
/**
* Helper method to check whether the specified flags are set
* @param flags
* @param check
* @return boolean indicating whether flags are set
*/
protected static final boolean checkFlags(int flags, int check){
return (flags & check) == check;
}
/**
* Set the threadLocal project description
*
* Only intended to be used by implementors and package.
*
* This should be used in the following pattern:
* try {
* setThreadLocaProjectDesc(prjDesc);
* fireEvent();
* } finally {
* setThreadLocalProjectDesc(null);
* }
*
* @param currentDesc
* @return the previously set thread local project desc (or null)
*/
public ICProjectDescription setThreadLocalProjectDesc(ICProjectDescription currentDesc) {
ICProjectDescription current = currentThreadProjectDescription.get();
currentThreadProjectDescription.set(currentDesc);
return current;
}
}