/******************************************************************************* * 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.properties; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.cdt.managedbuilder.core.IConfiguration; import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo; import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IPreferenceNodeVisitor; import org.eclipse.core.runtime.preferences.IScopeContext; import org.osgi.service.prefs.BackingStoreException; import de.innot.avreclipse.AVRPlugin; import de.innot.avreclipse.core.preferences.BuildConfigurationScope; /** * Container for the Project Properties of an AVR Project. * <p> * This class maintains the global project settings (which currently is only the "per config" flag) * and a list of {@link AVRProjectProperties} objects which contain all other project properties * which can be either global for the project or for each build configuration. * </p> * <p> * For read access instantiate this class for a project and call * {@link #getConfigurationProperties(IConfiguration)}. This method will return either the * properties for the given <code>IConfiguration</code> or the project properties if the "per * config" flag has not been set (by the user). * </p> * <p> * To modify the properties either the {@link #getPropsForConfig(IConfiguration, boolean)} or the * {@link #getProjectProperties()} methods can be used to get the properties for a * <code>IConfiguration</code> (regardless of the "per config" flag), respectively the project * properties. * </p> * <p> * All modifications, including the current state of the "per config" flag are persisted with a call * to {@link #save()}. * </p> * * * @author Thomas Holland * @since 2.2 * */ public class ProjectPropertyManager { private static final String CLASSNAME = "avrtarget"; private static final String QUALIFIER = AVRPlugin.PLUGIN_ID + "/" + CLASSNAME; public static final String KEY_PER_CONFIG = "perConfig"; private static final boolean DEFAULT_PER_CONFIG = false; private static Map<IProject, ProjectPropertyManager> fsProjectMap = new HashMap<IProject, ProjectPropertyManager>(); public static ProjectPropertyManager getPropertyManager(IProject project) { ProjectPropertyManager projman = null; if (fsProjectMap.containsKey(project)) { projman = fsProjectMap.get(project); } else { projman = new ProjectPropertyManager(project); fsProjectMap.put(project, projman); // TODO add some kind of listener to remove projects if necessary } // reload the "per config" flag projman.load(); return projman; } /** * "per config" flag. If <code>true</code>, the project uses separate properties for each * build configuration. * */ private boolean fPerConfig; /** The project this description is for */ private final IProject fProject; /** * Instantiate Properties Description Object for the given Project. * * @param project */ private ProjectPropertyManager(IProject project) { Assert.isNotNull(project); fProject = project; load(); } /** * Set the "per config" flag. * <p> * If set to <code>true</code> the project will use separate properties for each build * configuration of the project. If set to <code>false</code> (the default value), only the * global project properties will be used for all build configurations. * </p> * * @param flag */ public void setPerConfig(boolean flag) { fPerConfig = flag; } /** * @return The current state of the "per config" flag. */ public boolean isPerConfig() { return fPerConfig; } /** * Get the Properties for the active build configuration or the global project properties if the * "per config" flag is false. * <p> * if no properties for the active configuration exists the global project properties are used * as a fallback. * </p> * * @return <code>AVRProjectProperies</code> with the requested properties. */ public AVRProjectProperties getActiveProperties() { if (fPerConfig) { // Get the active IConfiguration from our IProject IManagedBuildInfo bi = ManagedBuildManager.getBuildInfo(fProject); IConfiguration buildcfg = bi.getDefaultConfiguration(); return getConfigurationProperties(buildcfg); } // Project settings only return getProjectProperties(); } /** * Get the properties for the given <code>IConfiguration</code> or the global project * properties if the "per config" flag is false. * <p> * if no properties for the given configuration exists the global project properties are used as * a fallback. * </p> * * @param buildcfg * <code>IConfiguration</code> for which the properties are requested. * @return <code>AVRProjectProperies</code> with the requested properties. */ public AVRProjectProperties getConfigurationProperties(IConfiguration buildcfg) { return getConfigurationProperties(buildcfg, false); } /** * Get the properties for the given <code>IConfiguration</code>. * <p> * The force flag determines whether the "per config" flag is taken into account. * <ul> * <li>force = <code>true</code>: Return the properties for the build configuration, * regardless of the "per config" flag.</li> * <li>force = <code>false</code>: Return the global project properties if the "per config" * flag is also <code>false</code>.</li> * </ul> * <p> * If no properties for the given build configuration exist, the project settings are copied. * </p> * * @param buildcfg * <code>IConfiguration</code> for which the properties are requested. * @param force * Set to <code>true</code> to disregard the "per config" flag. * @param nocache * Return fresh properties, not from the cache. * @return <code>AVRProjectProperies</code> with the requested properties. */ public AVRProjectProperties getConfigurationProperties(IConfiguration buildcfg, boolean force) { // Test if the configuration belongs to this project IProject cfgproj = (IProject) buildcfg.getOwner(); if (!fProject.equals(cfgproj)) { throw new IllegalArgumentException("Configuration " + buildcfg.getId() + " does not belong to project " + fProject.getName()); } if (fPerConfig || force) { BuildConfigurationScope scope = new BuildConfigurationScope(buildcfg); // Test if the node for the configuration already exists. If no, we // create a new node by copying all values from the project settings boolean copyproject = !scope.configExists(QUALIFIER, buildcfg); IEclipsePreferences cfgprefs = getConfigurationPreferences(buildcfg); AVRProjectProperties newconfigprops; if (copyproject) { newconfigprops = new AVRProjectProperties(cfgprefs, getProjectProperties()); } else { newconfigprops = new AVRProjectProperties(cfgprefs); } return newconfigprops; } // global project settings return getProjectProperties(); } /** * Get the global project properties. * * @return <code>AVRProjectProperies</code> with the requested properties. */ public AVRProjectProperties getProjectProperties() { return new AVRProjectProperties(getProjectPreferences(fProject)); } /** * Get the default properties. * <p> * Unlike the other get???Properties() methods, the properties returned by this call are not * backed with a storage. Calls to save() will have no effect. It should only be used to extract * the default values. * </p> * * @return */ public static AVRProjectProperties getDefaultProperties() { return new AVRProjectProperties(getDefaultPreferences()); } /** * Loads the Properties from the property storage. * <p> * Currently only the "per Config" flag is loaded. * </p> */ public void load() { IEclipsePreferences projectprefs = getProjectPreferences(fProject); fPerConfig = projectprefs.getBoolean(KEY_PER_CONFIG, DEFAULT_PER_CONFIG); } /** * Save all modified properties. * <p> * This will save the current "per config" flag. * </p> * * @throws BackingStoreException * on any errors writing to the backing store. */ public void save() throws BackingStoreException { // Save the "per config" flag IEclipsePreferences projectprefs = getProjectPreferences(fProject); projectprefs.putBoolean(KEY_PER_CONFIG, fPerConfig); projectprefs.flush(); } /** * Remove all configuration properties for which the referenced build configuration does not * exist anymore. */ public void sync(final List<String> allcfgids) throws BackingStoreException { // TODO This method does not work yet. // Calling it will cause some exceptions later on for reasons unknown. // For now do nothing // if (false) // return; // get list of all our configuration properties IEclipsePreferences projprops = getProjectPreferences(fProject); projprops.accept(new IPreferenceNodeVisitor() { public boolean visit(IEclipsePreferences node) throws BackingStoreException { String name = node.name(); // nodes starting with "de.innot.avreclipse" are // configuration property nodes if (name.startsWith("de.innot.avreclipse")) { // Check if the id is in the list of all ids if (!allcfgids.contains(name)) { // The configuration does not exist anymore // remove the node from the preferences node.removeNode(); } return false; } // try the children return true; } }); projprops.flush(); } private static IEclipsePreferences getDefaultPreferences() { return new DefaultScope().getNode(QUALIFIER); } private static IEclipsePreferences getProjectPreferences(IProject project) { IScopeContext scope = new ProjectScope(project); return scope.getNode(QUALIFIER); } private static IEclipsePreferences getConfigurationPreferences(IConfiguration buildcfg) { IScopeContext scope = new BuildConfigurationScope(buildcfg); return scope.getNode(QUALIFIER); } }