gt: 		if (project != null)
context: /**
   * Aptana Studio
   * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
   * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
   * Please see the license.html included with this distribution for details.
   * Any modifications to this file must keep this entire header intact.
   */
  package com.aptana.editor.common.contentassist;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.resources.ProjectScope;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.preferences.IEclipsePreferences;
  import org.eclipse.core.runtime.preferences.InstanceScope;
  import org.eclipse.jface.preference.IPreferenceStore;
  import org.eclipse.jface.resource.ImageDescriptor;
  import org.eclipse.jface.resource.ImageRegistry;
  import org.eclipse.swt.graphics.Image;
  import org.eclipse.ui.texteditor.ChainedPreferenceStore;
  import org.osgi.service.prefs.BackingStoreException;
  
  import com.aptana.core.CorePlugin;
  import com.aptana.core.IUserAgent;
  import com.aptana.core.IUserAgentManager;
  import com.aptana.core.logging.IdeLog;
  import com.aptana.core.util.ArrayUtil;
  import com.aptana.core.util.ResourceUtil;
  import com.aptana.core.util.StringUtil;
  import com.aptana.editor.common.CommonEditorPlugin;
  import com.aptana.editor.common.IDebugScopes;
  import com.aptana.ui.epl.UIEplPlugin;
  
  public class UserAgentManager implements IUserAgentManager
  {
  
  	/**
  	 * A reference to the singleton UserAgentManager object
  	 */
  	private static UserAgentManager INSTANCE;
  
  	/**
  	 * The delimiter used between a list of user agent ids in the preference value
  	 */
  	private static final String USER_AGENT_DELIMITER = ","; //$NON-NLS-1$
  
  	/**
  	 * The delimiter used between each entries where an entry defines a nature and its active user agents
  	 */
  	private static final String ENTRY_DELIMITER = ";"; //$NON-NLS-1$
  
  	/**
  	 * The delimiter used between the nature and the user agent list in a entry
  	 */
  	private static final String NAME_VALUE_SEPARATOR = ":"; //$NON-NLS-1$
  
  	/**
  	 * A mapping of nature ID to an array of user agent ids. The user agent id array contains the ids which are
  	 * currently active for the given nature. This is mainly a in-memory cache of the string representation in the
  	 * preference key. This structure is generated by {@link #loadPreference()} and is converted to a preference key
  	 * value via {@link #savePreference()}
  	 */
  	private static Map<String, String[]> ACTIVE_USER_AGENTS_BY_NATURE_ID;
  
  	/**
  	 * Grab the singleton instance of the UserAgentManager. This method is responsible for creating the instance if it
  	 * does not exist. The instance is initialized (processes extension point and preference key) when it is created
  	 * 
  	 * @return Returns the singleton instance of this class
  	 */
  	public synchronized static UserAgentManager getInstance()
  	{
  		if (INSTANCE == null)
  		{
  			INSTANCE = new UserAgentManager();
  			INSTANCE.loadPreference();
  		}
  
  		return INSTANCE;
  	}
  
  	/**
  	 * A registry of images used to maintain icons for each user agent
  	 */
  	private ImageRegistry imageRegistry;
  
  	/**
  	 * Make sure no one accept the class itself can instantiate this class
  	 */
  	private UserAgentManager()
  	{
  	}
  
  	/**
  	 * Given a project, return the list of active user agent IDs. The current implementation processes only the first
  	 * (primary) nature ID of the given project. Secondary natures may be taken into consideration at a later point in
  	 * time
  	 * 
  	 * @param project
  	 *            An {@link IProject}.
  	 * @return Returns an array of user agent IDs for the main mature of the given project. In case the given project is
  	 *         null, an empty string array is returned.
  	 */
  	public String[] getActiveUserAgentIDs(IProject project)
  	{
  		if (project == null)
  		{
  			return ArrayUtil.NO_STRINGS;
  		}
  		// Extract the natures from the given project
  		String[] natureIDs = getProjectNatures(project);
  
  		// Look at the project-scope preferences for the active agents.
  		ProjectScope scope = new ProjectScope(project);
  		IEclipsePreferences node = scope.getNode(CommonEditorPlugin.PLUGIN_ID);
  		if (node != null)
  		{
  			String agents = node.get(IPreferenceConstants.USER_AGENT_PREFERENCE, null);
  			if (agents != null)
  			{
  				Map<String, String[]> userAgents = extractUserAgents(agents);
  				return getActiveUserAgentIDs(userAgents, natureIDs);
  			}
  		}
  		// In case we did not find any project-specific settings, use the project's nature IDs to grab the agents that
  		// were set in the workspace settings.
  		return getActiveUserAgentIDs(natureIDs);
  	}
  
  	/**
  	 * Extract the aptana project natures.
  	 * 
  	 * @param project
  	 * @return An array of project nature ids.
  	 */
  	private static String[] getProjectNatures(IProject project)
  	{
  		String[] natureIDs = ArrayUtil.NO_STRINGS;
  		try
  		{
  			natureIDs = ResourceUtil.getAptanaNatures(project.getDescription());
  		}
  		catch (CoreException e)
  		{
  			IdeLog.logWarning(CommonEditorPlugin.getDefault(), "Problem detecting the project's nature IDs for " //$NON-NLS-1$
  					+ project.getName(), e, IDebugScopes.CONTENT_ASSIST);
  		}
  		return natureIDs;
  	}
  
  	/**
  	 * Given a list of nature IDs, return the list of active user agent IDs. The current implementation processes only
  	 * the first (primary) nature ID. The signature allows for multiple nature IDs in case secondary natures need to be
  	 * taken into consideration at a later point in time
  	 * 
  	 * @param natureIDs
  	 *            An array of nature IDs to process
  	 * @return Returns an array of user agent IDs
  	 */
  	private String[] getActiveUserAgentIDs(String... natureIDs)
  	{
  		return getActiveUserAgentIDs(ACTIVE_USER_AGENTS_BY_NATURE_ID, natureIDs);
  	}
  
  	/**
  	 * Given a map of nature-ids to user-agents, and a list of nature-ids, return the matching user-agents.
  	 * 
  	 * @param userAgents
  	 * @param natureIDs
  	 */
  	private String[] getActiveUserAgentIDs(Map<String, String[]> userAgents, String... natureIDs)
  	{
  		if (!ArrayUtil.isEmpty(natureIDs))
  		{
  			// NOTE: We now loop through the natures and find the first match. if no matches, return empty array.
  
  			for (String natureID : natureIDs)
  			{
  				if (userAgents.containsKey(natureID))
  				{
  					return userAgents.get(natureID);
  				}
  			}
  		}
  
  		IdeLog.logWarning(CommonEditorPlugin.getDefault(), "UserAgentManager - Got empty natures list", //$NON-NLS-1$
  				IDebugScopes.CONTENT_ASSIST);
  		return ArrayUtil.NO_STRINGS;
  	}
  
  	/**
  	 * Returns an array of UserAgent instances for a given an {@link IProject}. This method uses
  	 * {@link #getActiveUserAgentIDs(IProject)} and therefore has the same limitations on natureIDs as described there
  	 * 
  	 * @return Returns an array array of UserAgent instances
  	 */
  	public IUserAgent[] getActiveUserAgents(IProject project)
  	{
  		return getUserAgentsByID(getActiveUserAgentIDs(project));
  	}
  
  	/**
  	 * Returns an array of UserAgent instances for a given list of nature IDs. This method uses
  	 * {@link #getActiveUserAgentIDs(String...)} and therefore has the same limitations on natureIDs as described there
  	 * 
  	 * @return Returns an array array of UserAgent instances
  	 */
  	public IUserAgent[] getActiveUserAgents(String... natureIDs)
  	{
  		return getUserAgentsByID(getActiveUserAgentIDs(natureIDs));
  	}
  
  	/**
  	 * Returns an array of UserAgents for all user agents known at runtime
  	 * 
  	 * @return Returns an array of UserAgent instances
  	 */
  	public IUserAgent[] getAllUserAgents()
  	{
  		return CorePlugin.getDefault().getUserAgentManager().getAllUserAgents();
  	}
  
  	public boolean addUserAgent(IUserAgent agent)
  	{
  		return CorePlugin.getDefault().getUserAgentManager().addUserAgent(agent);
  	}
  
  	/**
  	 * Returns a list of user agent IDs which are the default IDs for the specified nature. This method is typically
  	 * used in part to reset the list of active user agents for a given nature ID, particularly in the Content Assist
  	 * preference page.
  	 * 
  	 * @param natureID
  	 *            The nature ID to use when looking up the default user agent ID list
  	 * @return Returns an array of user agent IDs
  	 * @deprecated
  	 */
  	public String[] getDefaultUserAgentIDs(String natureID)
  	{
  		IUserAgent[] agents = getDefaultUserAgents(natureID);
  		String[] result = new String[agents.length];
  		for (int i = 0; i < agents.length; i++)
  		{
  			result[i] = agents[i].getID();
  		}
  		return result;
  	}
  
  	public IUserAgent[] getDefaultUserAgents(String natureID)
  	{
  		return CorePlugin.getDefault().getUserAgentManager().getDefaultUserAgents(natureID);
  	}
  
  	public Image getDisabledIcon(IUserAgent agent)
  	{
  		return getImage(agent, agent.getDisabledIconPath());
  	}
  
  	public Image getEnabledIcon(IUserAgent agent)
  	{
  		return getImage(agent, agent.getEnabledIconPath());
  	}
  
  	private Image getImage(IUserAgent agent, String iconPath)
  	{
  		Image result = null;
  
  		if (iconPath != null)
  		{
  			if (imageRegistry == null)
  			{
  				imageRegistry = new ImageRegistry();
  			}
  			String key = agent.getID() + '_' + iconPath;
  			result = imageRegistry.get(key);
  			if (result == null)
  			{
  				ImageDescriptor desc = CommonEditorPlugin.imageDescriptorFromPlugin(agent.getContributor(), iconPath);
  				imageRegistry.put(key, desc);
  			}
  			result = imageRegistry.get(key);
  		}
  
  		return result;
  	}
  
  	/**
  	 * Return an array of icons, one for each user agent ID in the userAgents array. The specified project provides a
  	 * list of natures that is used to determine the list of active user agents via
  	 * {@link #getActiveUserAgents(IProject)}. These user agents are compared to the user agents passed into this
  	 * method. All user agents that are not in the specified array will return disabled icons. All others return enabled
  	 * icons.
  	 * 
  	 * @param project
  	 * @param userAgents
  	 *            An array of user agent IDs
  	 * @return Returns an array of Images
  	 */
  	public Image[] getUserAgentImages(IProject project, String... userAgents)
  	{
  		IUserAgent[] activeUserAgents = getActiveUserAgents(project);
  		Set<String> enabledAgents;
  		if (userAgents == null)
  		{
  			enabledAgents = Collections.emptySet();
  		}
  		else
  		{
  			enabledAgents = new HashSet<String>(Arrays.asList(userAgents));
  		}
  		Image[] result = new Image[activeUserAgents.length];
  
  		Arrays.sort(activeUserAgents);
  
  		for (int i = 0; i < activeUserAgents.length; i++)
  		{
  			IUserAgent userAgent = activeUserAgents[i];
  
  			if (userAgent != null)
  			{
  				boolean isEnabled = enabledAgents.contains(userAgent.getID());
  				result[i] = isEnabled ? getEnabledIcon(userAgent) : getDisabledIcon(userAgent);
  			}
  		}
  
  		return result;
  	}
  
  	/**
  	 * Return an array of UserAgents, one for each recognized user agent ID
  	 * 
  	 * @param ids
  	 *            An array of user agent ids
  	 * @return Returns an array of UserAgent instances
  	 */
  	private IUserAgent[] getUserAgentsByID(String... ids)
  	{
  		List<IUserAgent> result = new ArrayList<IUserAgent>();
  
  		if (ids != null && ids.length > 0)
  		{
  			for (String id : ids)
  			{
  				IUserAgent userAgent = getUserAgentById(id);
  				if (userAgent != null)
  				{
  					result.add(userAgent);
  				}
  			}
  		}
  
  		return result.toArray(new IUserAgent[result.size()]);
  	}
  
  	public IUserAgent getUserAgentById(String id)
  	{
  		return CorePlugin.getDefault().getUserAgentManager().getUserAgentById(id);
  	}
  
  	/**
  	 * Process the preference key value associated with UserAgentManager. This method is automatically called when the
  	 * singleton instance is created. If the preference key value is somehow manipulated outside of UserAgentManager
  	 * (and it shouldn't be), then this method will need to be invoked to update the in-memory cache of
  	 * nature/user-agent info.
  	 */
  	void loadPreference()
  	{
  		// Grab preference value. We use a ChainedPreferenceStore to be able to migrate the preference location from the
  		// UIEplPlugin to the CommonEditorPlugin (the new location that we will use to save the
  		// IPreferenceConstants.USER_AGENT_PREFERENCE)
  		ChainedPreferenceStore chainedStore = new ChainedPreferenceStore(new IPreferenceStore[] {
  				CommonEditorPlugin.getDefault().getPreferenceStore(), UIEplPlugin.getDefault().getPreferenceStore() });
  		String preferenceValue = chainedStore.getString(IPreferenceConstants.USER_AGENT_PREFERENCE);
  
  		Map<String, String[]> result;
  		if (!StringUtil.isEmpty(preferenceValue))
  		{
  			result = extractUserAgents(preferenceValue);
  		}
  		else
  		{
  			result = new HashMap<String, String[]>();
  			// set defaults
  			for (String natureID : ResourceUtil.getAptanaNaturesMap().values())
  			{
  				result.put(natureID, getDefaultUserAgentIDs(natureID));
  			}
  		}
  
  		// cache result
  		ACTIVE_USER_AGENTS_BY_NATURE_ID = result;
  	}
  
  	private Map<String, String[]> extractUserAgents(String preferenceValue)
  	{
  		Map<String, String[]> result = new HashMap<String, String[]>();
  		if (preferenceValue.contains(NAME_VALUE_SEPARATOR))
  		{
  			// looks like the latest format for this pref key
  			String[] entries = preferenceValue.split(ENTRY_DELIMITER);
  
  			for (String entry : entries)
  			{
  				String[] nameValue = entry.split(NAME_VALUE_SEPARATOR);
  				String natureID = nameValue[0];
  
  				if (nameValue.length > 1)
  				{
  					String userAgentIDsString = nameValue[1];
  					String[] userAgentIDs = userAgentIDsString.split(USER_AGENT_DELIMITER);
  
  					result.put(natureID, userAgentIDs);
  				}
  				else
  				{
  					result.put(natureID, ArrayUtil.NO_STRINGS);
  				}
  			}
  		}
  		else
  		{
  			// assume this is an old style preference and update each nature ID with its user agent settings
  
  			// NOTE: We don't manipulate these arrays directly, so it should be fine to reference the same
  			// array for all natures
  			String[] userAgentIDs = preferenceValue.split(USER_AGENT_DELIMITER);
  
  			for (String natureID : ResourceUtil.getAptanaNaturesMap().values())
  			{
  				result.put(natureID, userAgentIDs);
  			}
  		}
  		return result;
  	}
  
  	/*
  	 * (non-Javadoc)
  	 * @see com.aptana.editor.common.contentassist.IUserAgentPreferenceManager#savePreference()
  	 */
  	public void savePreference()
  	{
  		savePreference(null, ACTIVE_USER_AGENTS_BY_NATURE_ID);
  	}
  
  	/*
  	 * (non-Javadoc)
  	 * @see
  	 * com.aptana.editor.common.contentassist.IUserAgentPreferenceManager#savePreference(org.eclipse.core.resources.
  	 * IProject, java.util.Map)
  	 */
  	public void savePreference(IProject project, Map<String, String[]> natureIdToUserAgents)
  	{
  		IEclipsePreferences preferences = null;
  