/*******************************************************************************
* 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());
}
}
}
}