/******************************************************************************* * Copyright (c) 2009 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 *******************************************************************************/ package org.eclipse.cdt.internal.ui.workingsets; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.ui.IMemento; import org.eclipse.ui.IWorkingSet; import org.eclipse.ui.IWorkingSetManager; import org.eclipse.ui.XMLMemento; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.newui.CDTPrefUtil; /** * The purveyor of working set configurations. It provides a current view of the {@linkplain IWorkingSetProxy * working set configurations} defined in the workspace, as well as a working-copy * {@linkplain WorkspaceSnapshot snapshot} of the same for making modifications. * * @author Christian W. Damus (cdamus) * * @since 6.0 * */ public class WorkingSetConfigurationManager { static final String TYPE_WORKING_SET_CONFIGS = "org.eclipse.cdt.ui.workingSetConfigurations"; //$NON-NLS-1$ static final String KEY_WORKING_SET = "workingSet"; //$NON-NLS-1$ static final String ATTR_NAME = "name"; //$NON-NLS-1$ static final String KEY_CONFIG = "config"; //$NON-NLS-1$ static final String KEY_PROJECT = "project"; //$NON-NLS-1$ static final String ATTR_CONFIG = "config"; //$NON-NLS-1$ static final String ATTR_FACTORY = "factory"; //$NON-NLS-1$ static IWorkingSetManager WS_MGR = CUIPlugin.getDefault().getWorkbench().getWorkingSetManager(); private static final WorkingSetConfigurationManager INSTANCE = new WorkingSetConfigurationManager(); private Map<String, IWorkingSetProxy> workingSets; private final Object storeLock = new Object(); private IMemento store; private final ISchedulingRule saveRule = new ISchedulingRule() { public boolean isConflicting(ISchedulingRule rule) { return rule == this; } public boolean contains(ISchedulingRule rule) { return rule == this; } }; /** * Not instantiable by clients. */ private WorkingSetConfigurationManager() { store = loadMemento(); new WorkingSetChangeTracker(); } /** * Obtains the default working set configuration manager. * * @return the working set configuration manager */ public static WorkingSetConfigurationManager getDefault() { return INSTANCE; } private Map<String, IWorkingSetProxy> getWorkingSetMap() { Map<String, IWorkingSetProxy> result; synchronized (storeLock) { if (workingSets == null) { load(); } result = workingSets; } return result; } /** * Obtains the current immutable view of the specified working set's configurations. These configurations * may be {@linkplain IWorkingSetConfiguration#activate() activated} to apply their settings to the * workspace, but they cannot be modified. * * @param name * the name of the working set to retrieve * @return the named working set, or <code>null</code> if there is none available by that name */ public IWorkingSetProxy getWorkingSet(String name) { return getWorkingSetMap().get(name); } /** * Obtains the current immutable view of all available working set configurations. These configurations * may be {@linkplain IWorkingSetConfiguration#activate() activated} to apply their settings to the * workspace, but they cannot be modified. * * @return the working set configurations */ public Collection<IWorkingSetProxy> getWorkingSets() { return getWorkingSetMap().values(); } /** * Creates a new mutable snapshot of the current working set configurations. This snapshot accepts * modifications and can be {@linkplain WorkspaceSnapshot#save() saved} to persist the changes. * * @return a working-copy of the working set configurations */ public WorkspaceSnapshot createWorkspaceSnapshot() { return new WorkspaceSnapshot().initialize(getWorkingSetMap()); } /** * <p> * Loads the working set configurations from storage. * </p> * <p> * <b>Note</b> that this method must only be called within the <tt>storeLock</tt> monitor. * </p> */ private void load() { workingSets = new java.util.HashMap<String, IWorkingSetProxy>(); for (IMemento next : store.getChildren(KEY_WORKING_SET)) { WorkingSetProxy ws = new WorkingSetProxy(); ws.loadState(next); if (ws.isValid()) { workingSets.put(ws.getName(), ws); } } } /** * <p> * Forgets the current view of the working set configurations. * </p> * <p> * <b>Note</b> that this method must only be called within the <tt>storeLock</tt> monitor. * </p> */ private void clear() { workingSets = null; } /** * Saves the working set configurations to storage. * * @param snapshot * the snapshot to save */ void save(WorkspaceSnapshot snapshot) { final XMLMemento memento = XMLMemento.createWriteRoot(TYPE_WORKING_SET_CONFIGS); for (IWorkingSetConfigurationElement next : snapshot.getWorkingSets()) { next.saveState(memento.createChild(KEY_WORKING_SET)); } save(memento); } /** * Records the specified memento as our new store and asynchronously saves it in a job. * * @param memento * the new store */ private void save(final XMLMemento memento) { synchronized (storeLock) { store = memento; clear(); } new Job(WorkingSetMessages.WSConfigManager_save_job) { { setRule(saveRule); setSystem(true); } @Override protected IStatus run(IProgressMonitor monitor) { File file = getStorage(); FileWriter writer = null; try { writer = new FileWriter(file); memento.save(writer); writer.close(); } catch (IOException e) { if (writer != null) { try { writer.close(); } catch (IOException e2) { // no recovery CUIPlugin.log(WorkingSetMessages.WSConfigManager_closeFailed, e); } } file.delete(); // it is corrupt; we won't be able to load it, later CUIPlugin.log(WorkingSetMessages.WSConfigManager_saveFailed, e); } return Status.OK_STATUS; } }.schedule(); } /** * Gets the file in which we persist the working set configurations. * * @return the file store */ private File getStorage() { IPath path = CUIPlugin.getDefault().getStateLocation().append("workingSetConfigs.xml"); //$NON-NLS-1$ return path.toFile(); } /** * Loads the working set configurations from storage. For compatibility, if the XML file is not available, * we load from the old preference setting format. * * @return the working set configuration store */ private IMemento loadMemento() { IMemento result = null; File file = getStorage(); if (file.exists()) { FileReader reader = null; try { reader = new FileReader(file); result = XMLMemento.createReadRoot(reader); reader.close(); } catch (Exception e) { result = null; if (reader != null) { try { reader.close(); } catch (IOException e2) { // no recovery CUIPlugin.log(WorkingSetMessages.WSConfigManager_closeFailed, e); } } CUIPlugin.log(WorkingSetMessages.WSConfigManager_loadFailed, e); } } if (result == null) { // fake one from the old preference storage format. This also // handles the case of no working set configurations ever being made @SuppressWarnings("deprecation") List<String> configSetStrings = CDTPrefUtil.readConfigSets(); result = XMLMemento.createWriteRoot(TYPE_WORKING_SET_CONFIGS); // collect the unordered entries by working set Map<String, IMemento> configMap = new HashMap<String, IMemento>(); for (String next : configSetStrings) { String[] bits = next.split(" "); //$NON-NLS-1$ if (bits.length >= 2) { String configName = bits[0]; String wsName = bits[1]; IMemento workingSet = configMap.get(wsName); if (workingSet == null) { workingSet = result.createChild(KEY_WORKING_SET); configMap.put(wsName, workingSet); } workingSet.putString(ATTR_NAME, wsName); IMemento config = workingSet.createChild(KEY_CONFIG); config.putString(ATTR_NAME, configName); int limit = bits.length - (bits.length % 2); for (int i = 2; i < limit; i += 2) { IMemento project = config.createChild(KEY_PROJECT); project.putString(ATTR_NAME, bits[i]); project.putString(ATTR_CONFIG, bits[i + 1]); } } } } return result; } // // Nested classes // /** * A working set manager listener that tracks name changes and removals of working sets to keep our * configurations in synch as much as possible. It updates the memento store directly in response to * changes in the working sets. * * @author Christian W. Damus (cdamus) * * @since 6.0 */ private class WorkingSetChangeTracker extends java.util.IdentityHashMap<IWorkingSet, String> implements IPropertyChangeListener { WorkingSetChangeTracker() { for (IWorkingSet next : WS_MGR.getWorkingSets()) { put(next, next.getName()); } WS_MGR.addPropertyChangeListener(this); } public void propertyChange(PropertyChangeEvent event) { String property = event.getProperty(); if (IWorkingSetManager.CHANGE_WORKING_SET_NAME_CHANGE.equals(property)) { handleNameChange((IWorkingSet) event.getNewValue()); } else if (IWorkingSetManager.CHANGE_WORKING_SET_REMOVE.equals(property)) { handleRemove((IWorkingSet) event.getOldValue()); } else if (IWorkingSetManager.CHANGE_WORKING_SET_ADD.equals(property)) { handleAdd((IWorkingSet) event.getNewValue()); } } private void handleNameChange(IWorkingSet workingSet) { synchronized (storeLock) { String oldName = get(workingSet); IMemento wsMemento = null; if (oldName != null) { for (IMemento next : store.getChildren(KEY_WORKING_SET)) { if (oldName.equals(next.getString(ATTR_NAME))) { wsMemento = next; break; } } } if (wsMemento != null) { // update the memento with the new name wsMemento.putString(ATTR_NAME, workingSet.getName()); // clone it XMLMemento newStore = XMLMemento.createWriteRoot(TYPE_WORKING_SET_CONFIGS); newStore.putMemento(store); // save it asynchronously save(newStore); } // and update our mapping put(workingSet, workingSet.getName()); } } private void handleRemove(IWorkingSet workingSet) { synchronized (storeLock) { String name = get(workingSet); if (name != null) { // remove the memento from the store XMLMemento newStore = XMLMemento.createWriteRoot(TYPE_WORKING_SET_CONFIGS); for (IMemento next : store.getChildren(KEY_WORKING_SET)) { if (!name.equals(next.getString(ATTR_NAME))) { newStore.createChild(KEY_WORKING_SET).putMemento(next); } } // save asynchronously save(newStore); } // and update our mapping remove(workingSet); } } private void handleAdd(IWorkingSet workingSet) { synchronized (storeLock) { put(workingSet, workingSet.getName()); } } } }