/*******************************************************************************
* Copyright (c) 2007, 2010 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
* James Blackburn (Broadcom Corp.)
*******************************************************************************/
package org.eclipse.cdt.internal.core.settings.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.CExternalSetting;
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICLanguageSetting;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
import org.eclipse.cdt.core.settings.model.ICStorageElement;
import org.eclipse.cdt.internal.core.settings.model.CExternalSettinsDeltaCalculator.ExtSettingsDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;
public class CExternalSettingsManager implements ICExternalSettingsListener, ICProjectDescriptionListener{
private static final int OP_CHANGED = 1;
private static final int OP_ADDED = 2;
private static final int OP_REMOVED = 3;
private static final QualifiedName EXTERNAL_SETTING_PROPERTY = new QualifiedName(CCorePlugin.PLUGIN_ID, "externalSettings"); //$NON-NLS-1$
private static final String EXTERNAL_SETTING_STORAGE_ID = CCorePlugin.PLUGIN_ID + ".externalSettings"; //$NON-NLS-1$
private Map<String, FactoryDescriptor> fFactoryMap = new HashMap<String, FactoryDescriptor>();
private static CExternalSettingsManager fInstance;
private CExternalSettingsManager(){
}
public void startup(){
CProjectDescriptionManager.getInstance().addCProjectDescriptionListener(this, CProjectDescriptionEvent.DATA_APPLIED
| CProjectDescriptionEvent.LOADED);
}
public void shutdown(){
for(Iterator<FactoryDescriptor> iter = fFactoryMap.values().iterator(); iter.hasNext();){
FactoryDescriptor dr = iter.next();
dr.shutdown();
}
fFactoryMap.clear();
CProjectDescriptionManager.getInstance().removeCProjectDescriptionListener(this);
}
public static CExternalSettingsManager getInstance(){
if(fInstance == null){
fInstance = new CExternalSettingsManager();
}
return fInstance;
}
/**
* A simple class representing an external settings container.
* These are uniquely identifiable by the factoryId + factory
* specific container id
*/
public final static class CContainerRef {
private final String fFactoryId;
private final String fContainerId;
public CContainerRef(String factoryId, String containerId){
fFactoryId = factoryId;
fContainerId = containerId;
}
public String getFactoryId() {
return fFactoryId;
}
public String getContainerId() {
return fContainerId;
}
@Override
public boolean equals(Object obj) {
if(obj == this)
return true;
if(obj == null)
return false;
if(!(obj instanceof CContainerRef))
return false;
CContainerRef other = (CContainerRef)obj;
if(!fContainerId.equals(other.fContainerId))
return false;
return fFactoryId.equals(other.fFactoryId);
}
@Override
public int hashCode() {
return fFactoryId.hashCode() + fContainerId.hashCode();
}
@Override
public String toString() {
return fFactoryId.toString() + " : " + fContainerId.toString(); //$NON-NLS-1$
}
}
private static class ContainerDescriptor {
private FactoryDescriptor fFactoryDr;
private CExternalSettingsHolder fHolder;
private CExternalSettingsContainer fContainer;
/** Stash error messages so we're not too noisy if things go wrong */
private static Set<String> failingProvidersMessages;
private ContainerDescriptor(FactoryDescriptor factoryDr,
String containerId,
IProject project,
ICConfigurationDescription cfgDes,
CExternalSetting[] previousSettings){
fFactoryDr = factoryDr;
try {
fContainer = fFactoryDr.getFactory().createContainer(containerId, project, cfgDes, previousSettings);
} catch (CoreException e) {
if (failingProvidersMessages == null)
failingProvidersMessages = new HashSet<String>();
// only report the error once per session
if (!failingProvidersMessages.contains(e.getMessage()))
CCorePlugin.log(e.getMessage());
failingProvidersMessages.add(e.getMessage());
}
if(fContainer == null)
fContainer = NullContainer.INSTANCE;
}
public CExternalSetting[] getExternalSettings(){
if(fHolder == null){
fHolder = new CExternalSettingsHolder();
fHolder.setExternalSettings(fContainer.getExternalSettings(), false);
}
return fHolder.getExternalSettings();
}
}
/**
* A dummy SettingsContainer with 0 CExternalSettings
*/
static class NullContainer extends CExternalSettingsContainer {
static final NullContainer INSTANCE = new NullContainer();
@Override
public CExternalSetting[] getExternalSettings() {
return new CExternalSetting[0];
}
}
private static class NullFactory extends CExternalSettingContainerFactory {
static NullFactory INSTANCE = new NullFactory();
@Override
public CExternalSettingsContainer createContainer(String id,
IProject project, ICConfigurationDescription cfgDes, CExternalSetting[] previousSettings) throws CoreException {
return NullContainer.INSTANCE;
}
}
private class FactoryDescriptor {
private CExternalSettingContainerFactory fFactory;
private String fId;
private FactoryDescriptor(String id){
fId = id;
}
private CExternalSettingContainerFactory getFactory(){
if(fFactory == null){
fFactory = createFactory(fId);
fFactory.startup();
fFactory.addListener(CExternalSettingsManager.this);
}
return fFactory;
}
private CExternalSettingContainerFactory createFactory(String id) {
if(id.equals(CfgExportSettingContainerFactory.FACTORY_ID))
return CfgExportSettingContainerFactory.getInstance();
else if(id.equals(ExtensionContainerFactory.FACTORY_ID))
return ExtensionContainerFactory.getInstance();
return NullFactory.INSTANCE;
}
public void shutdown(){
if(fFactory != null){
fFactory.removeListener(CExternalSettingsManager.this);
fFactory.shutdown();
fFactory = null;
}
}
}
private interface ICfgContainer {
ICConfigurationDescription getConfguration(boolean write);
}
private static class CfgContainer implements ICfgContainer {
private ICConfigurationDescription fCfgDes;
CfgContainer(ICConfigurationDescription cfgDes){
fCfgDes = cfgDes;
}
public ICConfigurationDescription getConfguration(boolean write) {
return fCfgDes;
}
}
private class CfgContainerRefInfoContainer {
private ICfgContainer fCfgContainer;
private CSettingsRefInfo fRefInfo;
private boolean fWriteWasRequested;
CfgContainerRefInfoContainer(ICfgContainer container){
fCfgContainer = container;
}
public CSettingsRefInfo getRefInfo(boolean write) {
if(fRefInfo == null
|| (write && !fWriteWasRequested)){
ICConfigurationDescription cfg = fCfgContainer.getConfguration(write);
fRefInfo = CExternalSettingsManager.this.getRefInfo(cfg, write);
fWriteWasRequested |= write;
}
return fRefInfo;
}
}
private static class HolderContainer {
private CfgContainerRefInfoContainer fRIContainer;
private CRefSettingsHolder fHolder;
private boolean fWriteWasRequested;
private CContainerRef fCRef;
HolderContainer(CfgContainerRefInfoContainer cr, CContainerRef cref){
fRIContainer = cr;
fCRef = cref;
}
CRefSettingsHolder getHolder(boolean write){
if(fHolder == null
|| (write && !fWriteWasRequested)){
CSettingsRefInfo ri = fRIContainer.getRefInfo(write);
fHolder = ri.get(fCRef);
fWriteWasRequested |= write;
}
return fHolder;
}
void setHolder(CRefSettingsHolder holder){
fRIContainer.getRefInfo(true).put(holder);
fWriteWasRequested = true;
fHolder = holder;
}
void removeHolder(){
fWriteWasRequested = true;
fHolder = null;
fRIContainer.getRefInfo(true).remove(fCRef);
}
}
private static class CfgListCfgContainer implements ICfgContainer{
private ProjDesCfgList fList;
private int fNum;
CfgListCfgContainer(ProjDesCfgList list, int num){
fList = list;
fNum = num;
}
public ICConfigurationDescription getConfguration(boolean write) {
return fList.get(fNum, write);
}
}
/**
* A simple container type that contains a Project Description & and associated list
* of configuration descriptions.
*/
private static class ProjDesCfgList {
private ICProjectDescription fProjDes;
private List<ICConfigurationDescription> fCfgList = new ArrayList<ICConfigurationDescription>();
public ProjDesCfgList(ICProjectDescription des, Set<String> idSet){
fProjDes = des;
ICConfigurationDescription[] cfgs = des.getConfigurations();
for(int i = 0; i < cfgs.length; i++){
if(idSet == null || idSet.contains(cfgs[i].getId()))
fCfgList.add(cfgs[i]);
}
}
public boolean isWritable(){
return !fProjDes.isReadOnly();
}
public ICConfigurationDescription get(int num, boolean write) {
if(write && fProjDes.isReadOnly()){
makeWritable();
}
return fCfgList.get(num);
}
private void makeWritable(){
ICProjectDescription writeDes = CProjectDescriptionManager.getInstance().getProjectDescription(fProjDes.getProject());
fProjDes = writeDes;
for(int i = 0; i < fCfgList.size(); i++){
ICConfigurationDescription cfg = fCfgList.get(i);
cfg = writeDes.getConfigurationById(cfg.getId());
if(cfg != null)
fCfgList.set(i, cfg);
else
fCfgList.remove(i);
}
}
public int size() {
return fCfgList.size();
}
}
private FactoryDescriptor getFactoryDescriptor(String id){
FactoryDescriptor dr = fFactoryMap.get(id);
if(dr == null){
dr = new FactoryDescriptor(id);
fFactoryMap.put(id, dr);
}
return dr;
}
CExternalSettingContainerFactory getFactory(String id){
FactoryDescriptor dr = getFactoryDescriptor(id);
return dr.getFactory();
}
private volatile IWorkspaceRunnable workspaceReconcileRunnable;
/**
* External settings call-back from the setting container factories
* to notify that settings have changed in a container.
*
* Schedules a runnable to update any referencing projects
*/
public void settingsChanged(final IProject project, final String cfgId, final CExternalSettingChangeEvent event) {
// Performance: If workspace reconcile already scheduled, then nothing to do...
// Current project && cfgId always null (i.e. always reconcile at the workspace level) but don't assume this for the future
final boolean isWorkspaceReconcile = project == null && cfgId == null;
IWorkspaceRunnable r = workspaceReconcileRunnable;
if (r != null && isWorkspaceReconcile)
return;
// Modifying the project description in an asynchronous runnable is likely bad...
// Unfortunately there's nothing else we can do as it's not safe to modify the referencing configurations in place
r = new IWorkspaceRunnable() {
@SuppressWarnings("unchecked")
public void run(IProgressMonitor monitor) throws CoreException {
// Unset workspaceReconcileRunnable
if (isWorkspaceReconcile)
workspaceReconcileRunnable = null;
ProjDesCfgList[] lists = null;
for (CExternalSettingsContainerChangeInfo info : event.getChangeInfos()) {
switch(info.getEventType()){
case CExternalSettingsContainerChangeInfo.CHANGED:
int flags = info.getChangeFlags();
if((flags & CExternalSettingsContainerChangeInfo.CONTAINER_CONTENTS) != 0){
if(lists == null)
// Potentially all configuration in all projects need to be considered for be
lists = createCfgListsForEvent(project, cfgId);
for (ProjDesCfgList list : lists) {
for(int i = 0; i < list.size(); i++){
CfgListCfgContainer cr = new CfgListCfgContainer(list, i);
if (processContainerChange(OP_CHANGED, cr, new CfgContainerRefInfoContainer(cr), info.getContainerInfo())) {
// Ensure the newly discovered settings are in the right order...
// we do this by removing and re-adding the references list
ICConfigurationDescription desc = cr.getConfguration(true);
Map<String, String> references = desc.getReferenceInfo();
desc.setReferenceInfo(Collections.EMPTY_MAP);
cr.getConfguration(true).setReferenceInfo(references);
}
}
}
}
break;
}
}
if (lists != null) {
final List<ICProjectDescription> list = getModifiedProjDesList(lists);
if(list.size() != 0) {
for(int i = 0; i < list.size(); i++) {
ICProjectDescription des = list.get(i);
CProjectDescriptionManager.getInstance().setProjectDescription(des.getProject(), des, false, monitor);
}
}
}
}
};
if (isWorkspaceReconcile)
workspaceReconcileRunnable = r;
// schedule / run in-line
CProjectDescriptionManager.runWspModification(r, new NullProgressMonitor());
}
private List<ICProjectDescription> getModifiedProjDesList(ProjDesCfgList[] lists){
List<ICProjectDescription> list = new ArrayList<ICProjectDescription>();
for(int i = 0; i < lists.length; i++){
if(lists[i].isWritable())
list.add(lists[i].fProjDes);
}
return list;
}
/**
* Returns an array of ProjDescCfgList corresponding to the passed in project + cfgId
* @param project project, or null
* @param cfgId configuration ID, or null
* @return ProjDescCfgList[]
*/
private ProjDesCfgList[] createCfgListsForEvent(IProject project, String cfgId){
ProjDesCfgList lists[];
Set<String> set = null;
if(project != null) {
if(cfgId != null){
set = new HashSet<String>();
set.add(cfgId);
}
ProjDesCfgList l = createCfgList(project, set);
if(l != null)
lists = new ProjDesCfgList[] { l };
else
lists = new ProjDesCfgList[0];
} else {
// Project is null -- add all CDT projects & configs in the workspace
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
List<ProjDesCfgList> list = new ArrayList<ProjDesCfgList>();
for (IProject p : projects){
ProjDesCfgList l = createCfgList(p, set);
if(l != null)
list.add(l);
}
lists = list.toArray(new ProjDesCfgList[list.size()]);
}
return lists;
}
private ProjDesCfgList createCfgList(IProject project, Set<String> cfgIdSet){
ICProjectDescription des = CProjectDescriptionManager.getInstance().getProjectDescription(project, false);
if(des == null)
return null;
return new ProjDesCfgList(des, cfgIdSet);
}
private boolean processContainerChange(int op,
ICfgContainer cr,
CfgContainerRefInfoContainer riContainer,
CContainerRef crInfo){
ICConfigurationDescription cfg = cr.getConfguration(false);
ExtSettingsDelta[] deltas = checkExternalSettingsChange(op,
cfg.getProjectDescription().getProject(), cfg, riContainer, crInfo);
if(deltas != null)
return CExternalSettingsDeltaProcessor.applyDelta(cr.getConfguration(true), deltas);
return false;
}
private static class RefInfoContainer{
CSettingsRefInfo fRefInfo;
int fInstanceId;
RefInfoContainer(CSettingsRefInfo ri, int id){
fRefInfo = ri;
fInstanceId = id;
}
}
private CSettingsRefInfo getRefInfo(ICConfigurationDescription cfg, boolean write){
if(write && cfg.isReadOnly())
throw new IllegalArgumentException(SettingsModelMessages.getString("CExternalSettingsManager.3")); //$NON-NLS-1$
RefInfoContainer cr = (RefInfoContainer)cfg.getSessionProperty(EXTERNAL_SETTING_PROPERTY);
CSettingsRefInfo ri;
boolean setCr = false;
if(cr == null){
ri = load(cfg);
if(ri == null)
ri = new CSettingsRefInfo();
setCr = true;
} else if (write && cr.fInstanceId != cfg.hashCode()){
ri = new CSettingsRefInfo(cr.fRefInfo);
setCr = true;
} else {
ri = cr.fRefInfo;
setCr = false;
}
if(setCr){
cr = new RefInfoContainer(ri, cfg.hashCode());
cfg.setSessionProperty(EXTERNAL_SETTING_PROPERTY, cr);
}
return ri;
}
private CSettingsRefInfo load(ICConfigurationDescription cfg){
try {
ICStorageElement el = cfg.getStorage(EXTERNAL_SETTING_STORAGE_ID, false);
if(el != null){
CSettingsRefInfo ri = new CSettingsRefInfo(el);
return ri;
}
} catch (CoreException e) {
CCorePlugin.log(e);
}
return null;
}
/**
* Respond to Project Description events.
* - DATA_APPLIED: Data has been applied, and the description is still
* writable, store cached external settings into the configuration
* - LOADED: Check whether a reconcile is needed and update the settings atomically
*/
public void handleEvent(CProjectDescriptionEvent event) {
switch(event.getEventType()){
case CProjectDescriptionEvent.DATA_APPLIED: {
ICProjectDescription des = event.getNewCProjectDescription();
if(des == null)
return;
ICConfigurationDescription[] cfgs = des.getConfigurations();
for(int i = 0; i < cfgs.length; i++){
ICConfigurationDescription cfg = cfgs[i];
RefInfoContainer cr = (RefInfoContainer)cfg.getSessionProperty(EXTERNAL_SETTING_PROPERTY);
if(cr != null/* && cr.fInstanceId != cfg.hashCode()*/){
store(cfg, cr.fRefInfo);
}
}
break;
}
case CProjectDescriptionEvent.LOADED:
// If the project description has no references, short-circuit:
boolean needsReconcile = false;
for (ICConfigurationDescription desc : event.getNewCProjectDescription().getConfigurations()) {
if (!desc.getReferenceInfo().isEmpty() ||
(desc.getExternalSettingsProviderIds() != null && desc.getExternalSettingsProviderIds().length > 0)) {
needsReconcile = true;
break;
}
}
if (!needsReconcile)
return;
// Note using an asynchronous get / set here is bad.
// Unfortunately there's no other way to make this work without re-writing the project model to allow
// us to reconcile / update the cached configuration during load
final IProject project = event.getProject();
IWorkspaceRunnable r = new IWorkspaceRunnable(){
@SuppressWarnings("unchecked")
public void run(IProgressMonitor monitor) throws CoreException {
if (!project.isAccessible())
return;
ProjDesCfgList list = new ProjDesCfgList(CoreModel.getDefault().getProjectDescription(project), null);
boolean changed = false;
for(int i = 0; i < list.size(); i++){
CfgListCfgContainer cfgCr = new CfgListCfgContainer(list, i);
CfgContainerRefInfoContainer ric = new CfgContainerRefInfoContainer(cfgCr);
CContainerRef[] refs = ric.getRefInfo(false).getReferences();
for(int k = 0; k < refs.length; k++) {
if(processContainerChange(OP_CHANGED, cfgCr, new CfgContainerRefInfoContainer(cfgCr), refs[k])) {
// Ensure the newly discovered settings are in the right order...
// we do this by removing and re-adding the references list
ICConfigurationDescription desc = cfgCr.getConfguration(true);
Map<String, String> references = desc.getReferenceInfo();
desc.setReferenceInfo(Collections.EMPTY_MAP);
cfgCr.getConfguration(true).setReferenceInfo(references);
changed = true;
}
}
}
if (changed)
CProjectDescriptionManager.getInstance().setProjectDescription(project, list.fProjDes);
}
};
CProjectDescriptionManager.runWspModification(r, null);
break;
}
}
private void store(ICConfigurationDescription cfg, CSettingsRefInfo ri){
try {
ICStorageElement el = cfg.getStorage(EXTERNAL_SETTING_STORAGE_ID, true);
el.clear();
ri.serialize(el);
} catch (CoreException e) {
CCorePlugin.log(e);
}
}
public void containerContentsChanged(ICConfigurationDescription cfg, CContainerRef cr){
CfgContainer ccr = new CfgContainer(cfg);
processContainerChange(OP_CHANGED, ccr, new CfgContainerRefInfoContainer(ccr), cr);
}
public void addContainer(ICConfigurationDescription cfg, CContainerRef cr){
CfgContainer ccr = new CfgContainer(cfg);
processContainerChange(OP_ADDED, ccr, new CfgContainerRefInfoContainer(ccr), cr);
}
public void removeContainer(ICConfigurationDescription cfg, CContainerRef cr){
CfgContainer ccr = new CfgContainer(cfg);
processContainerChange(OP_REMOVED, ccr, new CfgContainerRefInfoContainer(ccr), cr);
}
public CContainerRef[] getReferences(ICConfigurationDescription cfg, String factoryId){
CSettingsRefInfo info = getRefInfo(cfg, false);
return info.getReferences(factoryId);
}
private ExtSettingsDelta[] checkExternalSettingsChange(int op,
IProject proj,
ICConfigurationDescription cfgDes,
CfgContainerRefInfoContainer riContainer,
CContainerRef cr){
HolderContainer hCr = new HolderContainer(riContainer, cr);
CRefSettingsHolder holder = hCr.getHolder(false);
if(holder == null && op == OP_ADDED){
holder = new CRefSettingsHolder(cr);
hCr.setHolder(holder);
}
if(holder == null)
return null;
CExternalSetting[] newSettings = null;
CExternalSetting[] oldSettings = hCr.getHolder(false).getExternalSettings();
// ensure that the configuration exported external settings are cached even if this is a REMOVE operation
FactoryDescriptor dr = getFactoryDescriptor(cr.getFactoryId());
ContainerDescriptor cdr = new ContainerDescriptor(dr, cr.getContainerId(), proj, cfgDes, oldSettings);
newSettings = cdr.getExternalSettings();
if (op == OP_REMOVED)
newSettings = null;
ExtSettingsDelta[] deltas = CExternalSettinsDeltaCalculator.getInstance().getSettingChange(newSettings, oldSettings);
if(deltas != null) {
CRefSettingsHolder holder1 = hCr.getHolder(true);
holder1.setExternalSettings(newSettings, false);
holder1.setReconsiled(true);
}
if(op == OP_REMOVED)
hCr.removeHolder();
return deltas;
}
public void restoreSourceEntryDefaults(ICConfigurationDescription cfg){
CfgContainer cr = new CfgContainer(cfg);
CfgContainerRefInfoContainer ric = new CfgContainerRefInfoContainer(cr);
CExternalSetting[] settings = ric.getRefInfo(false).createExternalSettings();
ExtSettingsDelta[] deltas = CExternalSettinsDeltaCalculator.getInstance().getSettingChange(settings, null);
if(deltas != null){
CExternalSettingsDeltaProcessor.applySourceEntriesChange(cfg, deltas);
}
}
public void restoreOutputEntryDefaults(ICConfigurationDescription cfg){
CfgContainer cr = new CfgContainer(cfg);
CfgContainerRefInfoContainer ric = new CfgContainerRefInfoContainer(cr);
CExternalSetting[] settings = ric.getRefInfo(false).createExternalSettings();
ExtSettingsDelta[] deltas = CExternalSettinsDeltaCalculator.getInstance().getSettingChange(settings, null);
if(deltas != null){
CExternalSettingsDeltaProcessor.applyOutputEntriesChange(cfg, deltas);
}
}
public void restoreDefaults(ICLanguageSetting ls, int entryKinds){
ICConfigurationDescription cfg = ls.getConfiguration();
CfgContainer cr = new CfgContainer(cfg);
CfgContainerRefInfoContainer ric = new CfgContainerRefInfoContainer(cr);
CExternalSetting[] settings = ric.getRefInfo(false).createExternalSettings();
ExtSettingsDelta[] deltas = CExternalSettinsDeltaCalculator.getInstance().getSettingChange(settings, null);
if(deltas != null){
CExternalSettingsDeltaProcessor.applyDelta(ls, deltas, entryKinds);
}
}
}