/*******************************************************************************
* Copyright (c) 2008, 2011 Thomas Holland (thomas@innot.de) 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:
* Thomas Holland - initial API and implementation
*******************************************************************************/
package de.innot.avreclipse.core.avrdude;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
import de.innot.avreclipse.AVRPlugin;
import de.innot.avreclipse.core.preferences.AVRDudePreferences;
/**
* Manages the list of {@link ProgrammerConfig} objects in the preferences.
* <p>
* This manager has methods to
* <ul>
* <li>get configs from the preferences: {@link #getConfig(String)},</li>
* <li>create new configs: {@link #createNewConfig()},</li>
* <li>save configs: {@link #saveConfig(ProgrammerConfig)} and</li>
* <li>delete configs: {@link #deleteConfig(ProgrammerConfig)}</li>
* </p>
* <p>
* The manager also has methods to get a list of all available programmers and their names:
* {@link #getAllConfigIDs()} and {@link #getAllConfigNames()}.
* </p>
* <p>
* To improve access times all retreived configs are stored in an internal cache.
* </p>
* <p>
* This class implements the singleton pattern and can be accessed with the static
* {@link #getDefault()} method.
* </p>
*
* @author Thomas Holland
* @since 2.2
*
*/
public class ProgrammerConfigManager {
/**
* The prefix for programmer configuration id values. This is appended with a running number to
* get the real id.
*/
private final static String CONFIG_PREFIX = "programmerconfig.";
/** Static singleton instance */
private static ProgrammerConfigManager fInstance = null;
/** Cache of all Configs that have been used in this session */
private final Map<String, ProgrammerConfig> fConfigsCache;
/**
* List of all IDs that have been given out for new configs, but have not yet been saved. Used
* to avoid duplicate IDs, even when {@link #createNewConfig()} gets called multiple times.
*/
private final List<String> fPendingIds;
/**
* The preferences this manager works on.
*
* @see AVRDudePreferences#getConfigPreferences()
*/
private final IEclipsePreferences fPreferences;
/**
* Gets the session <code>ProgrammerConfigManager</code>.
*
* @return <code>ProgrammerConfigManager</code> for the current Eclipse session.
*/
public static ProgrammerConfigManager getDefault() {
if (fInstance == null) {
fInstance = new ProgrammerConfigManager();
}
return fInstance;
}
// Private to prevent instantiation of this class.
private ProgrammerConfigManager() {
// Set up Preferences and the internal lists
fPreferences = AVRDudePreferences.getConfigPreferences();
fConfigsCache = new HashMap<String, ProgrammerConfig>();
fPendingIds = new ArrayList<String>();
}
/**
* Create a new ProgrammerConfig.
* <p>
* The returned ProgrammerConfig is filled with some default values. It is not created in the
* preference store.
* </p>
* <p>
* Call {@link #saveConfig(ProgrammerConfig)} with the returned config to persist any
* modifications and to add the newly created config to the list of all existing configs.
* </p>
*
* @return A new <code>ProgrammerConfig</code>
*/
public ProgrammerConfig createNewConfig() {
// The id has the form "programmerconfig.#" where # is a running
// number.
// Find the first free id.
// Check both the list of existing config nodes in the preferences and
// the list of pending config ids (ids that have been assigned, but not
// yet saved).
String newid = null;
int i = 1;
try {
do {
newid = CONFIG_PREFIX + i++;
} while (fPreferences.nodeExists(newid) || fPendingIds.contains(newid));
} catch (BackingStoreException bse) {
// TODO What shall we do if we can't access the Preferences?
// For now log an error and return null.
logException(bse);
return null;
}
ProgrammerConfig newconfig = new ProgrammerConfig(newid);
fPendingIds.add(newid);
return newconfig;
}
/**
* Get the {@link ProgrammerConfig} with the given ID.
* <p>
* If the config has been requested before, a reference to the config in the internal cache is
* returned. All modifications to the returned config will affect the config in the cache.
* </p>
* <p>
* While these changes are only persisted when saveConfig() is called, it is usually better to
* use the {@link #getConfigEditable(ProgrammerConfig)} call to get a safely modifiable config.
* </p>
*
* @see #getConfigEditable(ProgrammerConfig)
*
* @param id
* <code>String</code> with an ID value.
* @return The requested <code>ProgrammerConfig</code> or <code>null</code> if no config
* with the given ID exists.
*/
public ProgrammerConfig getConfig(String id) {
// Test for empty / null id
if (id == null)
return null;
if (id.length() == 0)
return null;
// Test if the config is already in the cache
if (fConfigsCache.containsKey(id)) {
return fConfigsCache.get(id);
}
// The config was not in the cache
// The node must exist, otherwise return null
try {
if (!fPreferences.nodeExists(id)) {
return null;
}
} catch (BackingStoreException bse) {
// TODO What shall we do if we can't access the Preferences?
// For now log an error and return null.
logException(bse);
return null;
}
// Load the Config from the Preferences
Preferences cfgprefs = fPreferences.node(id);
ProgrammerConfig config = new ProgrammerConfig(id, cfgprefs);
fConfigsCache.put(id, config);
return config;
}
/**
* Return an safely modifiable copy of the given config.
* <p>
* The returned config is not backed by the cache, so any modifications will not be visible
* until the {@link #saveConfig(ProgrammerConfig) method is called with the returned config.<p>
* </p>
*
* @param sourceconfig
* Source <code>ProgrammerConfig</code> to clone.
* @return New <code>ProgrammerConfig</code> with the same properties as the source config.
*/
public ProgrammerConfig getConfigEditable(ProgrammerConfig sourceconfig) {
// Clone the source config
ProgrammerConfig cloneconfig = new ProgrammerConfig(sourceconfig);
return cloneconfig;
}
/**
* Test if the given ID is valid, i.e. a <code>ProgrammerConfig</code> with the given ID
* exists in the Preferences.
*
* @param id
* <code>String</code> with the ID value to test
* @return <code>true</code> if a config with the given ID exists in the Preferences.
*/
public boolean isValidId(String id) {
// Test the cache first (quicker)
if (fConfigsCache.containsKey(id)) {
return true;
}
// Not in the cache, try the preferences
try {
if (fPreferences.nodeExists(id)) {
return true;
}
} catch (BackingStoreException bse) {
// TODO What shall we do if we can't access the Preferences?
// For now log an error and return false.
logException(bse);
}
// Id not found anywhere
return false;
}
/**
* Deletes the given configuration from the preference storage area.
* <p>
* Note: This Object is still valid and further calls to {@link #saveConfig(ProgrammerConfig)}
* will add this configuration back to the preference storage.
* </p>
*
* @throws BackingStoreException
*/
public void deleteConfig(ProgrammerConfig config) throws BackingStoreException {
String id = config.getId();
// If the config is in the cache, remove it from the cache
if (fConfigsCache.containsKey(id)) {
fConfigsCache.remove(id);
}
// Remove the Preference node for the config and flush the preferences
// If the node does not exist do nothing - no need to create the node
// just to remove it again
if (fPreferences.nodeExists(id)) {
Preferences cfgnode = fPreferences.node(id);
cfgnode.removeNode();
fPreferences.flush();
}
}
/**
* Save the given Configuration to the persistent storage.
* <p>
* Only {@link ProgrammerConfig} objects obtained with the {@link #getConfigEditable(String)}
* method should be passed to this method, however this is currently not enforced.
* </p>
*
* @param config
* <code>ProgrammerConfig</code> to be saved.
* @throws BackingStoreException
* If this configuration cannot be written to the preference storage area.
*/
public void saveConfig(ProgrammerConfig config) throws BackingStoreException {
// save the config
config.save(getConfigPreferences(config));
// If the config is already in the cache update the cached config to the
// new values
if (fConfigsCache.containsKey(config.getId())) {
ProgrammerConfig oldconfig = fConfigsCache.get(config.getId());
oldconfig.loadFromConfig(config);
} else {
// Add the new config to the cache
fConfigsCache.put(config.getId(), config);
}
// Remove from the pending id list (if it is a newly created config)
fPendingIds.remove(config.getId());
}
/**
* Gets a list of all Programmer configuration ID values currently in the preferences.
*
* @return <code>Set<String></code> with all configuration id values.
*/
public Set<String> getAllConfigIDs() {
// All Programmer Configurations are children of the rootnode in the
// preferences. So fetch all children and add them to a Set.
Set<String> allconfigs = new HashSet<String>();
String[] confignames;
try {
confignames = fPreferences.childrenNames();
} catch (BackingStoreException bse) {
// TODO What shall we do if we can't access the Preferences?
// For now log an error and return an empty list
logException(bse);
return allconfigs;
}
for (String conf : confignames) {
allconfigs.add(conf);
}
return allconfigs;
}
/**
* Get a list of all programmer configuration names currently in the preferences.
* <p>
* The list is returned as a mapping of id values to config names.
* </p>
*
* @return <code>Map<String id, String name></code>
*/
public Map<String, String> getAllConfigNames() {
// Get all configs ids, then load all configs and
// add their names to a Map
Set<String> allids = getAllConfigIDs();
Map<String, String> idNameMap = new HashMap<String, String>(allids.size());
for (String id : allids) {
ProgrammerConfig cfg = getConfig(id);
idNameMap.put(id, cfg.getName());
}
return idNameMap;
}
/**
* Get the preference node for the given configuration
*
* @param config
* @return
*/
private Preferences getConfigPreferences(ProgrammerConfig config) {
String id = config.getId();
return fPreferences.node(id);
}
/**
* Log an BackingStoreException.
*
* @param bse
* <code>BackingStoreException</code> to log.
*/
private void logException(BackingStoreException bse) {
// TODO Check if we really should do this here or if we just throw the
// Exception all the way up to the GUI code to show an error dialog,
// like in the saveConfig() and deleteConfig() methods (where this
// Exception is more likely to happen as something is actually written
// to the Preferences)
//
Status status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Can't access the list of avrdude configuration preferences", bse);
AVRPlugin.getDefault().log(status);
}
}