/*******************************************************************************
* Copyright (c) 2009, 2013 Andrew Gvozdev 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:
* Andrew Gvozdev - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core.language.settings.providers;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.language.settings.providers.ICListenerAgent;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsBroadcastingProvider;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsChangeEvent;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsChangeListener;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsEditableProvider;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsProvider;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsProvidersKeeper;
import org.eclipse.cdt.core.language.settings.providers.LanguageSettingsManager;
import org.eclipse.cdt.core.language.settings.providers.LanguageSettingsSerializableProvider;
import org.eclipse.cdt.core.language.settings.providers.LanguageSettingsStorage;
import org.eclipse.cdt.core.language.settings.providers.ScannerDiscoveryLegacySupport;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;
import org.eclipse.cdt.internal.core.XmlUtil;
import org.eclipse.cdt.internal.core.model.Util;
import org.eclipse.cdt.internal.core.settings.model.CConfigurationSpecSettings;
import org.eclipse.cdt.internal.core.settings.model.IInternalCCfgInfo;
import org.eclipse.cdt.internal.core.settings.model.SettingsModelMessages;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.osgi.util.NLS;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Helper class handling serialization and notifications for language settings entries {@link ICLanguageSettingEntry}.
*/
public class LanguageSettingsProvidersSerializer {
public static final String PROVIDER_EXTENSION_POINT_ID = LanguageSettingsExtensionManager.PROVIDER_EXTENSION_POINT_ID;
public static final String ATTR_ID = LanguageSettingsExtensionManager.ATTR_ID;
public static final String ATTR_NAME = LanguageSettingsExtensionManager.ATTR_NAME;
public static final String ATTR_CLASS = LanguageSettingsExtensionManager.ATTR_CLASS;
public static final String ELEM_PROVIDER = LanguageSettingsExtensionManager.ELEM_PROVIDER;
public static final String ELEM_LANGUAGE_SCOPE = LanguageSettingsExtensionManager.ELEM_LANGUAGE_SCOPE;
public static final String JOB_FAMILY_SERIALIZE_LANGUAGE_SETTINGS_PROJECT = "CDT_JOB_FAMILY_SERIALIZE_LANGUAGE_SETTINGS_PROJECT"; //$NON-NLS-1$
public static final String JOB_FAMILY_SERIALIZE_LANGUAGE_SETTINGS_WORKSPACE = "CDT_JOB_FAMILY_SERIALIZE_LANGUAGE_SETTINGS_WORKSPACE"; //$NON-NLS-1$
private static final String PREFERENCE_WORSPACE_PROVIDERS_SET = "language.settings.providers.workspace.prefs.toggle"; //$NON-NLS-1$
private static final String CPROJECT_STORAGE_MODULE_LANGUAGE_SETTINGS_PROVIDERS = "org.eclipse.cdt.core.LanguageSettingsProviders"; //$NON-NLS-1$
private static final String STORAGE_WORKSPACE_LANGUAGE_SETTINGS = "language.settings.xml"; //$NON-NLS-1$
private static final String STORAGE_PROJECT_PATH = ".settings/language.settings.xml"; //$NON-NLS-1$
private static final int PROGRESS_MONITOR_SCALE = 100;
private static final int TICKS_SERIALIZING = 1 * PROGRESS_MONITOR_SCALE;
private static final String ELEM_PLUGIN = "plugin"; //$NON-NLS-1$
private static final String ELEM_EXTENSION = "extension"; //$NON-NLS-1$
private static final String ATTR_EXTENSION_POINT = "point"; //$NON-NLS-1$
private static final String ELEM_PROJECT = "project"; //$NON-NLS-1$
private static final String ELEM_CONFIGURATION = "configuration"; //$NON-NLS-1$
private static final String ELEM_PROVIDER_REFERENCE = "provider-reference"; //$NON-NLS-1$
private static final String ATTR_STORE_ENTRIES_WITH_PROJECT = "store-entries-with-project"; //$NON-NLS-1$
// those are for readability of xml only
private static final String ATTR_REF = "ref"; //$NON-NLS-1$
private static final String VALUE_REF_SHARED_PROVIDER = "shared-provider"; //$NON-NLS-1$
private static final String ATTR_COPY_OF = "copy-of"; //$NON-NLS-1$
private static final String VALUE_COPY_OF_EXTENSION = "extension"; //$NON-NLS-1$
/** Cache of true (raw) workspace providers */
private static Map<String, ILanguageSettingsProvider> rawGlobalWorkspaceProviders = new HashMap<String, ILanguageSettingsProvider>();
/** Cache of workspace providers wrappers */
private static Map<String, ILanguageSettingsProvider> globalWorkspaceProviders = new HashMap<String, ILanguageSettingsProvider>();
private static ListenerList fLanguageSettingsChangeListeners = new ListenerList(ListenerList.IDENTITY);
private static ILock serializingLock = Job.getJobManager().newLock();
private static ILock serializingLockWsp = Job.getJobManager().newLock();
/**
* Dummy class to represent ill-defined provider.
*/
private static class NotAccessibleProvider implements ILanguageSettingsProvider {
private final String id;
private NotAccessibleProvider(String providerId) {
this.id = providerId;
}
@Override
public List<ICLanguageSettingEntry> getSettingEntries(ICConfigurationDescription cfgDescription, IResource rc, String languageId) {
return null;
}
@Override
public String getName() {
return null;
}
@Override
public String getId() {
return id;
}
}
/**
* language settings provider listener-cfgDescription association
*/
private static class ListenerAssociation {
private ICListenerAgent listener;
private ICConfigurationDescription cfgDescription;
public ListenerAssociation(ICListenerAgent la, ICConfigurationDescription cfgd) {
listener = la;
cfgDescription = cfgd;
}
}
/**
* Wrapper for workspace providers to ensure level of indirection. That way workspace providers
* can be changed/replaced without notifying/changing the configurations which keep the providers
* in their lists.
*/
private static class LanguageSettingsWorkspaceProvider implements ILanguageSettingsProvider, ICListenerAgent {
private String providerId;
private int projectCount = 0;
private LanguageSettingsWorkspaceProvider(String id) {
Assert.isNotNull(id);
providerId = id;
}
@Override
public String getId() {
return providerId;
}
@Override
public String getName() {
ILanguageSettingsProvider rawProvider = getRawProvider();
String name = rawProvider!=null ? rawProvider.getName() : null;
return name;
}
@Override
public List<ICLanguageSettingEntry> getSettingEntries(ICConfigurationDescription cfgDescription, IResource rc, String languageId) {
ILanguageSettingsProvider rawProvider = getRawProvider();
List<ICLanguageSettingEntry> entries = rawProvider!=null ? rawProvider.getSettingEntries(cfgDescription, rc, languageId) : null;
return entries;
}
/**
* Do not cache the "raw" provider as workspace provider can be changed at any time.
*/
private ILanguageSettingsProvider getRawProvider() {
return LanguageSettingsProvidersSerializer.getRawWorkspaceProvider(providerId);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LanguageSettingsWorkspaceProvider) {
LanguageSettingsWorkspaceProvider that = (LanguageSettingsWorkspaceProvider) obj;
return providerId.equals(that.providerId);
}
return false;
}
/**
* Method toString() for debugging purposes.
*/
@SuppressWarnings("nls")
@Override
public String toString() {
return "id="+getId()+", name="+getName();
}
/**
* We count number of times <b>workspace</b> provider (not the raw one!) associated
* with a <b>project</b>. If a project includes it multiple times via different configurations
* it still counts as 1.
*/
private int getProjectCount() {
return projectCount;
}
private synchronized int incrementProjectCount() {
projectCount++;
return projectCount;
}
private synchronized int decrementProjectCount() {
projectCount--;
return projectCount;
}
@Override
public void registerListener(ICConfigurationDescription cfgDescription) {
// keep in mind that rawProvider can change externally
ILanguageSettingsProvider rawProvider = getRawProvider();
if (rawProvider instanceof ICListenerAgent) {
((ICListenerAgent) rawProvider).registerListener(null);
}
}
@Override
public void unregisterListener() {
// keep in mind that rawProvider can change externally
ILanguageSettingsProvider rawProvider = getRawProvider();
if (rawProvider instanceof ICListenerAgent) {
((ICListenerAgent) rawProvider).unregisterListener();
}
}
}
/**
* Language Settings Change Event implementation.
*/
private static class LanguageSettingsChangeEvent implements ILanguageSettingsChangeEvent {
private String projectName = null;
private Map<String/*cfg*/, LanguageSettingsDelta> deltaMap = new HashMap<String, LanguageSettingsDelta>();
/**
* The act of creating event resets internal delta count in configuration state.
* That implies that when the event is retrieved it must be fired or delta will go missing.
* That side effect is here to ensure atomic processing of firing & resetting the delta.
*/
public LanguageSettingsChangeEvent(ICProjectDescription prjDescription) {
if (!prjDescription.isReadOnly()) {
// The logic goes that we send notifications only for acting description but not for currently being prepared to set
String msg = "Project description " + prjDescription.getName() + " is expected to be read-only"; //$NON-NLS-1$ //$NON-NLS-2$
CCorePlugin.log(new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, msg, new Exception(msg)));
}
projectName = prjDescription.getName();
ICConfigurationDescription[] cfgDescriptions = prjDescription.getConfigurations();
for (ICConfigurationDescription cfgDescription : cfgDescriptions) {
if (cfgDescription instanceof IInternalCCfgInfo) {
CConfigurationSpecSettings specSettings = null;
try {
specSettings = ((IInternalCCfgInfo) cfgDescription).getSpecSettings();
} catch (CoreException e) {
CCorePlugin.log(e);
}
if (specSettings != null) {
String cfgId = cfgDescription.getId();
if (ScannerDiscoveryLegacySupport.isLanguageSettingsProvidersFunctionalityEnabled(prjDescription.getProject())) {
LanguageSettingsDelta delta = specSettings.dropDelta();
if (delta != null) {
deltaMap.put(cfgId, delta);
}
} else {
deltaMap.remove(cfgId);
}
} else {
IStatus ss = new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, "Internal error: Missing specSettings for " //$NON-NLS-1$
+ cfgDescription.getClass().getSimpleName());
CCorePlugin.log(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, ss.getMessage(), new CoreException(ss)));
}
}
}
}
@Override
public String getProjectName() {
return projectName;
}
@Override
public String[] getConfigurationDescriptionIds() {
return deltaMap.keySet().toArray(new String[deltaMap.size()]);
}
@SuppressWarnings("nls")
@Override
public String toString() {
return "LanguageSettingsChangeEvent for project=[" + getProjectName() + "]"
+ ", configurations=" + deltaMap.keySet();
}
@Override
public Set<IResource> getAffectedResources(String cfgId) {
LanguageSettingsDelta delta = deltaMap.get(cfgId);
if (delta != null) {
Set<String> paths = delta.getAffectedResourcePaths();
if (!paths.isEmpty()) {
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
Set<IResource> resources = new HashSet<IResource>();
for (String path : paths) {
IResource rc = project.findMember(path);
if (rc != null) {
resources.add(rc);
}
}
return resources;
}
}
return null;
}
}
/** static initializer */
static {
try {
loadLanguageSettingsWorkspace();
} catch (Throwable e) {
// log but swallow any exception
CCorePlugin.log("Error loading workspace language settings providers", e); //$NON-NLS-1$
}
}
/**
* Tells if language settings entries of the provider are persisted with the project
* (under .settings/ folder) or in workspace area. Persistence in the project area lets
* the entries migrate with the project.
*
* @param provider - provider to check the persistence mode.
* @return {@code true} if LSE persisted with the project or {@code false} if in the workspace.
*/
public static boolean isStoringEntriesInProjectArea(LanguageSettingsSerializableProvider provider) {
return provider.getPropertyBool(ATTR_STORE_ENTRIES_WITH_PROJECT);
}
/**
* Define where language settings are persisted for the provider.
*
* @param provider - provider to set the persistence mode.
* @param storeEntriesWithProject - {@code true} if with the project,
* {@code false} if in workspace area.
*/
public static void setStoringEntriesInProjectArea(LanguageSettingsSerializableProvider provider, boolean storeEntriesWithProject) {
provider.setPropertyBool(ATTR_STORE_ENTRIES_WITH_PROJECT, storeEntriesWithProject);
}
/**
* Determine location of the project store of language settings providers in the plug-in state area.
*
* @param store - name of the store.
* @return location of the store in the plug-in state area.
*/
private static IFile getStoreInProjectArea(IProject project) {
return project.getFile(STORAGE_PROJECT_PATH);
}
/**
* Determine location of the store in the plug-in state area.
*
* @param store - name of the store.
* @return location of the store in the plug-in state area.
*/
private static URI getStoreInWorkspaceArea(String store) {
IPath location = CCorePlugin.getDefault().getStateLocation().append(store);
return URIUtil.toURI(location);
}
/**
* Set and store user defined providers in workspace area.
*
* @param providers - array of user defined providers
* @throws CoreException in case of problems
*/
public static void setWorkspaceProviders(List<ILanguageSettingsProvider> providers) throws CoreException {
setWorkspaceProvidersInternal(providers);
serializeLanguageSettingsWorkspaceInBackground();
// generate preference change event for preference change listeners (value is not intended to be used)
IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(CCorePlugin.PLUGIN_ID);
prefs.putBoolean(PREFERENCE_WORSPACE_PROVIDERS_SET, ! prefs.getBoolean(PREFERENCE_WORSPACE_PROVIDERS_SET, false));
}
/**
* Internal method to set user defined providers in memory.
*
* @param providers - list of user defined providers. If {@code null}
* is passed user defined providers are cleared.
*/
private static void setWorkspaceProvidersInternal(List<ILanguageSettingsProvider> providers) {
Map<String, ILanguageSettingsProvider> rawNewProviders = new HashMap<String, ILanguageSettingsProvider>();
// add given providers
if (providers != null) {
for (ILanguageSettingsProvider provider : providers) {
if (isWorkspaceProvider(provider)) {
provider = rawGlobalWorkspaceProviders.get(provider.getId());
}
if (provider != null) {
rawNewProviders.put(provider.getId(), provider);
}
}
}
// fill the rest from extension registry
// this list is independent from the internal list of extensions in LanguageSettingsExtensionManager
for (String id : LanguageSettingsExtensionManager.getExtensionProviderIds()) {
if (!rawNewProviders.containsKey(id)) {
ILanguageSettingsProvider provider = LanguageSettingsExtensionManager.getExtensionProviderCopy(id, true);
if (provider == null) {
provider = LanguageSettingsExtensionManager.loadProvider(id);
}
if (provider != null) {
rawNewProviders.put(provider.getId(), provider);
}
}
}
// register listeners
List<ICListenerAgent> oldListeners = selectListeners(rawGlobalWorkspaceProviders.values());
List<ICListenerAgent> newListeners = selectListeners(rawNewProviders.values());
for (ICListenerAgent oldListener : oldListeners) {
if (!isInList(newListeners, oldListener)) {
LanguageSettingsWorkspaceProvider wspProvider = (LanguageSettingsWorkspaceProvider) globalWorkspaceProviders.get(((ILanguageSettingsProvider)oldListener).getId());
if (wspProvider != null && wspProvider.getProjectCount() > 0) {
oldListener.unregisterListener();
}
}
}
for (ICListenerAgent newListener : newListeners) {
if (!isInList(oldListeners, newListener)) {
LanguageSettingsWorkspaceProvider wspProvider = (LanguageSettingsWorkspaceProvider) globalWorkspaceProviders.get(((ILanguageSettingsProvider)newListener).getId());
if (wspProvider != null && wspProvider.getProjectCount() > 0) {
newListener.registerListener(null);
}
}
}
rawGlobalWorkspaceProviders = rawNewProviders;
}
/**
* Create event for language settings changes of workspace providers in a project.
*/
private static LanguageSettingsChangeEvent createEvent(ICProjectDescription prjDescription, List<String> providerIds) {
ICConfigurationDescription[] cfgDescriptions = prjDescription.getConfigurations();
for (ICConfigurationDescription cfgDescription : cfgDescriptions) {
if (cfgDescription instanceof ILanguageSettingsProvidersKeeper) {
for (ILanguageSettingsProvider provider : ((ILanguageSettingsProvidersKeeper) cfgDescription).getLanguageSettingProviders()) {
if (isWorkspaceProvider(provider) && providerIds.contains(provider.getId())) {
LanguageSettingsChangeEvent event = new LanguageSettingsChangeEvent(prjDescription);
if (event.getConfigurationDescriptionIds().length > 0) {
return event;
}
return null;
}
}
}
}
return null;
}
/**
* Compute events for language settings changes in workspace.
*/
private static List<LanguageSettingsChangeEvent> createLanguageSettingsChangeEvents(List<ILanguageSettingsBroadcastingProvider> providers) {
List<LanguageSettingsChangeEvent> events = new ArrayList<LanguageSettingsChangeEvent>();
List<String> providerIds = new ArrayList<String>();
for (ILanguageSettingsBroadcastingProvider provider : providers) {
providerIds.add(provider.getId());
}
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IProject[] projects = root.getProjects();
for (IProject project : projects) {
try {
if (project.isAccessible()) {
ICProjectDescription prjDescription = CCorePlugin.getDefault().getProjectDescription(project, false);
if (prjDescription != null) {
LanguageSettingsChangeEvent event = createEvent(prjDescription, providerIds);
if (event != null) {
events.add(event);
}
}
}
} catch (Throwable e) {
// log and swallow any exception
CCorePlugin.log("Error creating event about changes in workspace language settings providers, " //$NON-NLS-1$
+ "project=" + project.getName(), e); //$NON-NLS-1$
}
}
return events;
}
/**
* Save language settings providers of the workspace (global providers) to persistent storage.
* @throws CoreException
*/
public static void serializeLanguageSettingsWorkspace() throws CoreException {
URI uriStoreWsp = getStoreInWorkspaceArea(STORAGE_WORKSPACE_LANGUAGE_SETTINGS);
List<ILanguageSettingsBroadcastingProvider> broadcastingWorkspaceProviders = new ArrayList<ILanguageSettingsBroadcastingProvider>();
List<LanguageSettingsSerializableProvider> serializingWorkspaceProviders = new ArrayList<LanguageSettingsSerializableProvider>();
for (ILanguageSettingsProvider provider : rawGlobalWorkspaceProviders.values()) {
if (provider instanceof ILanguageSettingsBroadcastingProvider) {
broadcastingWorkspaceProviders.add((ILanguageSettingsBroadcastingProvider)provider);
}
if (provider instanceof LanguageSettingsSerializableProvider) {
if (!LanguageSettingsManager.isEqualExtensionProvider(provider, true)) {
serializingWorkspaceProviders.add((LanguageSettingsSerializableProvider)provider);
}
}
}
try {
List<LanguageSettingsChangeEvent> events = null;
if (serializingWorkspaceProviders.isEmpty()) {
java.io.File fileStoreWsp = new java.io.File(uriStoreWsp);
try {
serializingLockWsp.acquire();
fileStoreWsp.delete();
// manufacture events while inside the lock
events = createLanguageSettingsChangeEvents(broadcastingWorkspaceProviders);
} finally {
serializingLockWsp.release();
}
} else {
Document doc = XmlUtil.newDocument();
Element rootElement = XmlUtil.appendElement(doc, ELEM_PLUGIN);
Element elementExtension = XmlUtil.appendElement(rootElement, ELEM_EXTENSION,
new String[] {ATTR_EXTENSION_POINT, PROVIDER_EXTENSION_POINT_ID});
for (LanguageSettingsSerializableProvider provider : serializingWorkspaceProviders) {
provider.serialize(elementExtension);
}
try {
serializingLockWsp.acquire();
String eol = Util.getLineSeparator(uriStoreWsp);
if (eol == null) {
eol = Util.getDefaultLineSeparator();
}
XmlUtil.serializeXml(doc, uriStoreWsp, eol);
// manufacture events while inside the lock
events = createLanguageSettingsChangeEvents(broadcastingWorkspaceProviders);
} finally {
serializingLockWsp.release();
}
}
// notify the listeners outside the lock
for (LanguageSettingsChangeEvent event : events) {
notifyLanguageSettingsChangeListeners(event);
}
} catch (Exception e) {
String msg = "Internal error while trying to serialize language settings"; //$NON-NLS-1$
CCorePlugin.log(msg, e);
throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, msg, e));
}
}
/**
* Save language settings providers of the workspace (global providers) to persistent storage
* in background.
*/
public static void serializeLanguageSettingsWorkspaceInBackground() {
Job[] jobs = Job.getJobManager().find(JOB_FAMILY_SERIALIZE_LANGUAGE_SETTINGS_WORKSPACE);
for (Job job : jobs) {
if (job.getState() == Job.WAITING) {
// do not schedule if there is serializing job in queue already
return;
}
}
Job job = new WorkspaceJob(SettingsModelMessages.getString("LanguageSettingsProvidersSerializer.SerializeJobName")) { //$NON-NLS-1$
@Override
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
try {
monitor.beginTask(SettingsModelMessages.getString("LanguageSettingsProvidersSerializer.SerializingForWorkspace"), //$NON-NLS-1$
TICKS_SERIALIZING);
serializeLanguageSettingsWorkspace();
monitor.worked(TICKS_SERIALIZING);
return Status.OK_STATUS;
} catch (Throwable e) {
String msg = "Internal error running job of serializing language settings in workspace"; //$NON-NLS-1$
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, msg, e);
} finally {
monitor.done();
}
}
@Override
public boolean belongsTo(Object family) {
return family == JOB_FAMILY_SERIALIZE_LANGUAGE_SETTINGS_WORKSPACE;
}
};
job.setRule(ResourcesPlugin.getWorkspace().getRoot());
job.schedule();
}
/**
* Load language settings for workspace.
*/
public static void loadLanguageSettingsWorkspace() {
// ensure completion of any scheduled background serialization
try {
Job.getJobManager().join(JOB_FAMILY_SERIALIZE_LANGUAGE_SETTINGS_WORKSPACE, null);
} catch (OperationCanceledException e) {
return;
} catch (InterruptedException e) {
CCorePlugin.log(e);
// restore interrupted status
Thread.currentThread().interrupt();
}
List <ILanguageSettingsProvider> providers = null;
URI uriStoreWsp = getStoreInWorkspaceArea(STORAGE_WORKSPACE_LANGUAGE_SETTINGS);
Document doc = null;
try {
serializingLockWsp.acquire();
doc = XmlUtil.loadXml(uriStoreWsp);
} catch (Exception e) {
CCorePlugin.log("Can't load preferences from file " + uriStoreWsp, e); //$NON-NLS-1$
} finally {
serializingLockWsp.release();
}
if (doc != null) {
Element rootElement = doc.getDocumentElement();
NodeList providerNodes = rootElement.getElementsByTagName(ELEM_PROVIDER);
List<String> userDefinedProvidersIds = new ArrayList<String>(providerNodes.getLength());
providers = new ArrayList<ILanguageSettingsProvider>(providerNodes.getLength());
for (int i = 0; i < providerNodes.getLength(); i++) {
Node providerNode = providerNodes.item(i);
final String providerId = XmlUtil.determineAttributeValue(providerNode, ATTR_ID);
if (userDefinedProvidersIds.contains(providerId)) {
String msg = "Ignored an attempt to persist duplicate language settings provider, id=" + providerId; //$NON-NLS-1$
CCorePlugin.log(new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, msg, new Exception()));
continue;
}
userDefinedProvidersIds.add(providerId);
ILanguageSettingsProvider provider = null;
try {
provider = loadProvider(providerNode);
} catch (Exception e) {
CCorePlugin.log("Error initializing workspace language settings providers", e); //$NON-NLS-1$
}
if (provider == null) {
provider = new NotAccessibleProvider(providerId);
}
providers.add(provider);
}
}
setWorkspaceProvidersInternal(providers);
}
/**
* @noreference This method is not intended to be referenced by clients.
* It is public solely for benefit of JUnit testing.
*/
public static void serializeLanguageSettingsInternal(Element projectElementPrjStore, Element projectElementWspStore, ICProjectDescription prjDescription) {
ICConfigurationDescription[] cfgDescriptions = prjDescription.getConfigurations();
for (ICConfigurationDescription cfgDescription : cfgDescriptions) {
if (!(cfgDescription instanceof ILanguageSettingsProvidersKeeper))
continue;
// no lazy initialization as we may need to save 0 providers when it is different from default
Element elementConfiguration = XmlUtil.appendElement(projectElementPrjStore, ELEM_CONFIGURATION, new String[] {
ATTR_ID, cfgDescription.getId(),
ATTR_NAME, cfgDescription.getName(),
});
Element elementConfigurationWsp = null;
List<ILanguageSettingsProvider> providers = ((ILanguageSettingsProvidersKeeper) cfgDescription).getLanguageSettingProviders();
if (providers.size() > 0) {
Element elementExtension = null;
Element elementExtensionWsp = null;
for (ILanguageSettingsProvider provider : providers) {
if (isWorkspaceProvider(provider)) {
if (elementExtension == null) {
elementExtension = XmlUtil.appendElement(elementConfiguration, ELEM_EXTENSION, new String[] {
ATTR_EXTENSION_POINT, PROVIDER_EXTENSION_POINT_ID});
}
// Element elementProviderReference =
XmlUtil.appendElement(elementExtension, ELEM_PROVIDER_REFERENCE, new String[] {
ATTR_ID, provider.getId(),
ATTR_REF, VALUE_REF_SHARED_PROVIDER,
});
continue;
}
if (!(provider instanceof LanguageSettingsSerializableProvider)) {
if (elementExtension == null) {
elementExtension = XmlUtil.appendElement(elementConfiguration, ELEM_EXTENSION, new String[] {
ATTR_EXTENSION_POINT, PROVIDER_EXTENSION_POINT_ID});
}
// Element elementProvider =
XmlUtil.appendElement(elementExtension, ELEM_PROVIDER, new String[] {
ATTR_ID, provider.getId(),
ATTR_NAME, provider.getName(),
ATTR_CLASS, provider.getClass().getCanonicalName(),
});
} else if (LanguageSettingsManager.isEqualExtensionProvider(provider, true)) {
if (elementExtension == null) {
elementExtension = XmlUtil.appendElement(elementConfiguration, ELEM_EXTENSION, new String[] {
ATTR_EXTENSION_POINT, PROVIDER_EXTENSION_POINT_ID});
}
// Element elementProvider =
XmlUtil.appendElement(elementExtension, ELEM_PROVIDER, new String[] {
ATTR_ID, provider.getId(),
ATTR_COPY_OF, VALUE_COPY_OF_EXTENSION,
});
} else {
try {
LanguageSettingsSerializableProvider lss = (LanguageSettingsSerializableProvider) provider;
boolean isWspStorageAvailable = (projectElementWspStore != null) && (projectElementPrjStore != projectElementWspStore);
if (isStoringEntriesInProjectArea(lss) || !isWspStorageAvailable) {
if (elementExtension == null) {
elementExtension = XmlUtil.appendElement(elementConfiguration, ELEM_EXTENSION, new String[] {
ATTR_EXTENSION_POINT, PROVIDER_EXTENSION_POINT_ID});
}
lss.serialize(elementExtension);
} else {
if (elementExtension == null) {
elementExtension = XmlUtil.appendElement(elementConfiguration, ELEM_EXTENSION, new String[] {
ATTR_EXTENSION_POINT, PROVIDER_EXTENSION_POINT_ID});
}
if (elementExtensionWsp == null) {
if (elementConfigurationWsp == null) {
elementConfigurationWsp = XmlUtil.appendElement(projectElementWspStore, ELEM_CONFIGURATION, new String[] {
ATTR_ID, cfgDescription.getId(),
ATTR_NAME, cfgDescription.getName(),
});
}
elementExtensionWsp = XmlUtil.appendElement(elementConfigurationWsp, ELEM_EXTENSION, new String[] {
ATTR_EXTENSION_POINT, PROVIDER_EXTENSION_POINT_ID});
}
Element elementProviderWsp = XmlUtil.appendElement(elementExtensionWsp, ELEM_PROVIDER, new String[] {
ATTR_ID, provider.getId() }); // no attributes kept in workspace storage
// split storage
lss.serializeAttributes(elementExtension);
lss.serializeEntries(elementProviderWsp);
}
} catch (Throwable e) {
// protect from any exceptions from implementers
CCorePlugin.log("Exception trying serialize provider "+provider.getId(), e); //$NON-NLS-1$
}
}
}
}
}
}
/**
* Check if all the language settings providers in the project match defaults.
*/
private static boolean isEqualToDefaultProviders(ICProjectDescription prjDescription) {
ICConfigurationDescription[] cfgDescriptions = prjDescription.getConfigurations();
for (ICConfigurationDescription cfgDescription : cfgDescriptions) {
if (!(cfgDescription instanceof ILanguageSettingsProvidersKeeper)) {
continue;
}
String[] defaultIds = ((ILanguageSettingsProvidersKeeper) cfgDescription).getDefaultLanguageSettingsProvidersIds();
if (defaultIds == null) {
defaultIds = ScannerDiscoveryLegacySupport.getDefaultProviderIdsLegacy(cfgDescription);
}
// check size
List<ILanguageSettingsProvider> providers = ((ILanguageSettingsProvidersKeeper) cfgDescription).getLanguageSettingProviders();
if (providers.size() != defaultIds.length) {
return false;
}
// check ids
for (int i = 0; i < defaultIds.length; i++) {
ILanguageSettingsProvider provider = providers.get(i);
if (!provider.getId().equals(defaultIds[i])) {
return false;
}
}
// check equality (most expensive, so check last)
for (ILanguageSettingsProvider provider : providers) {
if (LanguageSettingsManager.isPreferShared(provider.getId())) {
if (!isWorkspaceProvider(provider)) {
return false;
}
} else {
if (!LanguageSettingsManager.isEqualExtensionProvider(provider, true)) {
return false;
}
}
}
}
return true;
}
/**
* Save language settings providers of a project to persistent storage.
*
* @param prjDescription - project description of the project.
* @throws CoreException if something goes wrong.
*/
public static void serializeLanguageSettings(ICProjectDescription prjDescription) throws CoreException {
IProject project = prjDescription.getProject();
try {
// Add the storage module to .cpoject and persist on disk as a side effect of adding
prjDescription.getStorage(CPROJECT_STORAGE_MODULE_LANGUAGE_SETTINGS_PROVIDERS, true);
} catch (CoreException e) {
CCorePlugin.log("Internal error while trying to serialize language settings", e); //$NON-NLS-1$
}
try {
// The storage could be split in two, one for provider properties, another one for entries,
// depending on provider flag
// Document to store in project area
Document docStorePrj = XmlUtil.newDocument();
Element projectElementStorePrj = XmlUtil.appendElement(docStorePrj, ELEM_PROJECT);
// Document to store in workspace area
Document docStoreWsp = XmlUtil.newDocument();
Element projectElementStoreWsp = XmlUtil.appendElement(docStoreWsp, ELEM_PROJECT);
URI uriStoreWsp = getStoreInWorkspaceArea(project.getName()+'.'+STORAGE_WORKSPACE_LANGUAGE_SETTINGS);
LanguageSettingsChangeEvent event = null;
try {
// Note that need for serialization may exist even if LS *entries* event delta is empty,
// as set of providers or their properties may differ
serializingLock.acquire();
if (!isEqualToDefaultProviders(prjDescription)) {
serializeLanguageSettingsInternal(projectElementStorePrj, projectElementStoreWsp, prjDescription);
}
// Absent store means default providers as specified in the toolchain
IFile fileStorePrj = getStoreInProjectArea(project);
boolean isProjectStoreEmpty = projectElementStorePrj.getChildNodes().getLength() == 0;
if (isProjectStoreEmpty) {
if (fileStorePrj.exists()) {
fileStorePrj.delete(true, null);
}
} else {
IContainer folder = fileStorePrj.getParent();
if (folder instanceof IFolder && !folder.exists()) {
((IFolder) folder).create(true, true, null);
}
XmlUtil.serializeXml(docStorePrj, fileStorePrj);
}
// project-specific location in workspace area
boolean isWorkspaceStoreEmpty = projectElementStoreWsp.getChildNodes().getLength() == 0;
if (isWorkspaceStoreEmpty) {
new java.io.File(uriStoreWsp).delete();
} else {
String eol = Util.getLineSeparator(uriStoreWsp);
if (eol == null) {
eol = Util.getDefaultLineSeparator(project);
}
XmlUtil.serializeXml(docStoreWsp, uriStoreWsp, eol);
}
// manufacture the event only if serialization was successful
event = new LanguageSettingsChangeEvent(prjDescription);
} finally {
serializingLock.release();
}
// notify the listeners outside the lock
if (event.getConfigurationDescriptionIds().length > 0) {
notifyLanguageSettingsChangeListeners(event);
}
} catch (Exception e) {
String msg = "Internal error while trying to serialize language settings"; //$NON-NLS-1$
CCorePlugin.log(msg, e);
throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, msg, e));
}
}
/**
* Save language settings providers of a project to persistent storage in background.
*
* @param prjDescription - project description of the project.
*/
public static void serializeLanguageSettingsInBackground(final ICProjectDescription prjDescription) {
Job job = new Job(SettingsModelMessages.getString("LanguageSettingsProvidersSerializer.SerializeJobName")) { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
monitor.beginTask(NLS.bind(SettingsModelMessages.getString("LanguageSettingsProvidersSerializer.SerializingForProject"), //$NON-NLS-1$
prjDescription.getName()), TICKS_SERIALIZING);
serializeLanguageSettings(prjDescription);
monitor.worked(TICKS_SERIALIZING);
return Status.OK_STATUS;
} catch (Throwable e) {
String msg = "Internal error running job of serializing language settings for project " + prjDescription.getName(); //$NON-NLS-1$
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, msg, e);
} finally {
monitor.done();
}
}
@Override
public boolean belongsTo(Object family) {
return family == JOB_FAMILY_SERIALIZE_LANGUAGE_SETTINGS_PROJECT;
}
};
ISchedulingRule rule = null;
IProject project = prjDescription.getProject();
if (project != null) {
IFile fileStorePrj = getStoreInProjectArea(project);
IContainer folder = fileStorePrj.getParent();
if (folder instanceof IFolder && !folder.exists()) {
try {
((IFolder) folder).create(true, true, null);
} catch (CoreException e) {
CCorePlugin.log(e);
}
}
if (folder.isAccessible()) {
rule = folder;
}
}
if (rule == null) {
rule = ResourcesPlugin.getWorkspace().getRoot();
}
job.setRule(rule);
job.schedule();
}
/**
* Load language settings to the project description from XML.
*
* @noreference This method is not intended to be referenced by clients.
* It is public solely for benefit of JUnit testing.
*/
public static void loadLanguageSettingsInternal(Element projectElementPrj, Element projectElementWsp, ICProjectDescription prjDescription) {
/*
<project>
<configuration id="cfg.id">
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
<provider .../>
<provider-reference id="..."/>
</extension>
</configuration>
</project>
*/
NodeList configurationNodes = projectElementPrj.getChildNodes();
for (int ic = 0; ic < configurationNodes.getLength(); ic++) {
Node cfgNode = configurationNodes.item(ic);
if (!isElementWithName(cfgNode, ELEM_CONFIGURATION)) {
continue;
}
List<ILanguageSettingsProvider> providers = new ArrayList<ILanguageSettingsProvider>();
String cfgId = XmlUtil.determineAttributeValue(cfgNode, ATTR_ID);
NodeList extensionNodes = cfgNode.getChildNodes();
for (int ie = 0; ie < extensionNodes.getLength(); ie++) {
Node extNode = extensionNodes.item(ie);
if (!isElementWithName(extNode, ELEM_EXTENSION)) {
continue;
}
NodeList providerNodes = extNode.getChildNodes();
for (int ip = 0; ip < providerNodes.getLength(); ip++) {
ILanguageSettingsProvider provider = null;
Node providerNode = providerNodes.item(ip);
if (isElementWithName(providerNode, ELEM_PROVIDER_REFERENCE)) {
String providerId = XmlUtil.determineAttributeValue(providerNode, ATTR_ID);
provider = getWorkspaceProvider(providerId);
} else if (isElementWithName(providerNode, ELEM_PROVIDER)) {
String providerClass = XmlUtil.determineAttributeValue(providerNode, ATTR_CLASS);
if (providerClass == null || providerClass.isEmpty()) {
// provider is copied from extension if "class" is not supplied
String providerId = XmlUtil.determineAttributeValue(providerNode, ATTR_ID);
provider = LanguageSettingsManager.getExtensionProviderCopy(providerId, true);
if (provider == null) {
String msg = "Internal Error trying to retrieve copy of extension provider id=" + providerId; //$NON-NLS-1$
CCorePlugin.log(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, msg, new Exception(msg)));
provider = LanguageSettingsManager.getWorkspaceProvider(providerId);
}
} else {
try {
provider = loadProvider(providerNode);
} catch (CoreException e) {
@SuppressWarnings("nls")
String msg = "Error loading provider class=[" + providerClass + "] "
+ "in project=" + prjDescription.getProject().getName() + ", cfg=[" + cfgId + "]";
CCorePlugin.log(msg, e);
}
if (provider instanceof LanguageSettingsSerializableProvider) {
LanguageSettingsSerializableProvider lss = (LanguageSettingsSerializableProvider) provider;
if (!isStoringEntriesInProjectArea(lss) && projectElementWsp != null) {
loadProviderEntries(lss, cfgId, projectElementWsp);
}
}
}
}
if (provider != null) {
providers.add(provider);
}
}
}
ICConfigurationDescription cfgDescription = prjDescription.getConfigurationById(cfgId);
setProvidersWithoutNotification(cfgDescription, providers);
}
}
/**
* Set providers into configuration description avoiding triggering an event.
*/
private static void setProvidersWithoutNotification(ICConfigurationDescription cfgDescription, List<ILanguageSettingsProvider> providers) {
if (cfgDescription instanceof ILanguageSettingsProvidersKeeper) {
((ILanguageSettingsProvidersKeeper) cfgDescription).setLanguageSettingProviders(providers);
if (cfgDescription instanceof IInternalCCfgInfo) {
try {
// swallow delta
((IInternalCCfgInfo) cfgDescription).getSpecSettings().dropDelta();
} catch (CoreException e) {
CCorePlugin.log(e);
}
}
}
}
/**
* Check if the node is {@code Element} and that the name matches.
*/
private static boolean isElementWithName(Node cfgNode, String name) {
return cfgNode instanceof Element && cfgNode.getNodeName().equals(name);
}
/**
* Load provider entries for the given configuration from XML Element.
*/
private static void loadProviderEntries(LanguageSettingsSerializableProvider provider,
String cfgId, Element projectElement) {
/*
<project>
<configuration id="cfg.id">
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
<provider .../>
<provider-reference id="..."/>
</extension>
</configuration>
</project>
*/
NodeList configurationNodes = projectElement.getChildNodes();
for (int ic = 0; ic < configurationNodes.getLength(); ic++) {
Node cfgNode = configurationNodes.item(ic);
if (!isElementWithName(cfgNode, ELEM_CONFIGURATION)) {
continue;
}
String cfgIdXml = XmlUtil.determineAttributeValue(cfgNode, ATTR_ID);
if (!cfgId.equals(cfgIdXml)) {
continue;
}
NodeList extensionNodes = cfgNode.getChildNodes();
for (int ie = 0; ie < extensionNodes.getLength(); ie++) {
Node extNode = extensionNodes.item(ie);
if (!isElementWithName(extNode, ELEM_EXTENSION)) {
continue;
}
NodeList providerNodes = extNode.getChildNodes();
for (int ip = 0; ip < providerNodes.getLength(); ip++) {
Node providerNode = providerNodes.item(ip);
if (!isElementWithName(providerNode, ELEM_PROVIDER)) {
continue;
}
String id = XmlUtil.determineAttributeValue(providerNode, ATTR_ID);
if (provider.getId().equals(id)) {
provider.loadEntries((Element) providerNode);
return;
}
}
}
}
}
/**
* Load provider from provider node.
*/
private static ILanguageSettingsProvider loadProvider(Node providerNode) throws CoreException {
String attrClass = XmlUtil.determineAttributeValue(providerNode, ATTR_CLASS);
ILanguageSettingsProvider provider = LanguageSettingsExtensionManager.instantiateProviderClass(attrClass);
if (provider instanceof LanguageSettingsSerializableProvider) {
((LanguageSettingsSerializableProvider)provider).load((Element) providerNode);
}
return provider;
}
/**
* Load language settings from workspace and project storages for the given project description.
* @param prjDescription - project description to load language settings.
*/
public static void loadLanguageSettings(ICProjectDescription prjDescription) {
IProject project = prjDescription.getProject();
IFile storeInPrjArea = getStoreInProjectArea(project);
boolean isStoreInProjectAreaExist = storeInPrjArea.exists();
if (isStoreInProjectAreaExist) {
Document doc = null;
try {
doc = XmlUtil.loadXml(storeInPrjArea);
Element rootElementPrj = doc.getDocumentElement(); // <project>
URI uriStoreWsp = getStoreInWorkspaceArea(project.getName()+'.'+STORAGE_WORKSPACE_LANGUAGE_SETTINGS);
Document docWsp = null;
try {
serializingLock.acquire();
docWsp = XmlUtil.loadXml(uriStoreWsp);
} finally {
serializingLock.release();
}
Element rootElementWsp = null; // <project>
if (docWsp != null) {
rootElementWsp = docWsp.getDocumentElement();
}
loadLanguageSettingsInternal(rootElementPrj, rootElementWsp, prjDescription);
} catch (Exception e) {
CCorePlugin.log("Can't load preferences from file " + storeInPrjArea.getLocation(), e); //$NON-NLS-1$
}
} else {
// If storage in project area does not exist set default providers defined in the tool-chain
for (ICConfigurationDescription cfgDescription : prjDescription.getConfigurations()) {
if (cfgDescription instanceof ILanguageSettingsProvidersKeeper) {
String[] ids = ((ILanguageSettingsProvidersKeeper) cfgDescription).getDefaultLanguageSettingsProvidersIds();
if (ids != null) {
List<ILanguageSettingsProvider> providers = new ArrayList<ILanguageSettingsProvider>(ids.length);
for (String id : ids) {
if (LanguageSettingsExtensionManager.isPreferShared(id)) {
providers.add(LanguageSettingsManager.getWorkspaceProvider(id));
} else {
providers.add(LanguageSettingsManager.getExtensionProviderCopy(id, true));
}
}
((ILanguageSettingsProvidersKeeper) cfgDescription).setLanguageSettingProviders(providers);
}
}
}
}
// propagate the preference to project properties
ScannerDiscoveryLegacySupport.defineLanguageSettingsEnablement(project);
}
/**
* Get Language Settings Provider from the list of workspace providers,
* see {@link #getWorkspaceProviders()}.
*
* @param id - ID of provider to find.
* @return the workspace provider. If provider is not defined - still workspace
* provider wrapper is created and returned.
*/
public static ILanguageSettingsProvider getWorkspaceProvider(String id) {
ILanguageSettingsProvider provider = globalWorkspaceProviders.get(id);
if (provider == null) {
provider = new LanguageSettingsWorkspaceProvider(id);
globalWorkspaceProviders.put(id, provider);
}
return provider;
}
/**
* Helper method to get to real underlying provider collecting entries as opposed
* to wrapper which is normally used for workspace provider.
* @see #isWorkspaceProvider(ILanguageSettingsProvider)
*
* @param id - ID of the provider.
* @return raw underlying provider.
*/
public static ILanguageSettingsProvider getRawWorkspaceProvider(String id) {
return rawGlobalWorkspaceProviders.get(id);
}
/**
* Get Language Settings Providers defined in the workspace. That includes
* user-defined providers and after that providers defined as extensions via
* {@code org.eclipse.cdt.core.LanguageSettingsProvider} extension point.
* Note that this returns wrappers around workspace provider so underlying
* provider could be replaced internally without need to change configuration.
* See also {@link #getRawWorkspaceProvider(String)}.
*
* @return list of workspace providers.
*/
public static List<ILanguageSettingsProvider> getWorkspaceProviders() {
ArrayList<ILanguageSettingsProvider> workspaceProviders = new ArrayList<ILanguageSettingsProvider>();
for (ILanguageSettingsProvider rawProvider : rawGlobalWorkspaceProviders.values()) {
workspaceProviders.add(getWorkspaceProvider(rawProvider.getId()));
}
return workspaceProviders;
}
/**
* Checks if the provider is a workspace level provider.
* This method is intended to check providers retrieved from a configuration.
* Raw providers from {@link #getRawWorkspaceProvider(String)}
* are not considered as workspace providers.
*
* @param provider - provider to check.
* @return {@code true} if the given provider is workspace provider, {@code false} otherwise.
*/
public static boolean isWorkspaceProvider(ILanguageSettingsProvider provider) {
return provider instanceof LanguageSettingsWorkspaceProvider;
}
/**
* Check that this particular element is in the list.
*/
private static <T> boolean isInList(Collection<T> list, T element) {
// list.contains(element) won't do it as we are interested in exact object, not in equal object
for (T elem : list) {
if (elem == element)
return true;
}
return false;
}
/**
* Check that this particular element is in the association list.
*/
private static boolean isListenerInTheListOfAssociations(Collection<ListenerAssociation> list, ICListenerAgent element) {
for (ListenerAssociation la : list) {
// we are interested in exact object, not in equal object
if (la.listener == element)
return true;
}
return false;
}
/**
* Get a providers list including only providers of type {@link ICListenerAgent}
* for a given project description - collecting from all configurations.
*/
private static List<ICListenerAgent> getListeners(ICProjectDescription prjDescription) {
List<ICListenerAgent> listeners = new ArrayList<ICListenerAgent>();
if (prjDescription != null) {
for (ICConfigurationDescription cfgDescription : prjDescription.getConfigurations()) {
if (cfgDescription instanceof ILanguageSettingsProvidersKeeper) {
List<ILanguageSettingsProvider> providers = ((ILanguageSettingsProvidersKeeper) cfgDescription).getLanguageSettingProviders();
for (ILanguageSettingsProvider provider : providers) {
if (provider instanceof ICListenerAgent) {
ICListenerAgent listener = (ICListenerAgent) provider;
if (!isInList(listeners, listener)) {
listeners.add(listener);
}
}
}
}
}
}
return listeners;
}
/**
* Pick from the list providers which are listeners, i.e. instances of type {@link ICListenerAgent}.
*/
private static List<ICListenerAgent> selectListeners(Collection<ILanguageSettingsProvider> values) {
List<ICListenerAgent> listeners = new ArrayList<ICListenerAgent>();
for (ILanguageSettingsProvider provider : values) {
if (provider instanceof ICListenerAgent)
listeners.add((ICListenerAgent) provider);
}
return listeners;
}
/**
* Get a providers list including only providers of type {@link ICListenerAgent}
* for a given project description - collecting from all configurations.
*/
private static List<ListenerAssociation> getListenersAssociations(ICProjectDescription prjDescription) {
List<ListenerAssociation> associations = new ArrayList<ListenerAssociation>();
if (prjDescription != null) {
for (ICConfigurationDescription cfgDescription : prjDescription.getConfigurations()) {
if (cfgDescription instanceof ILanguageSettingsProvidersKeeper) {
List<ILanguageSettingsProvider> providers = ((ILanguageSettingsProvidersKeeper) cfgDescription).getLanguageSettingProviders();
List<ICListenerAgent> listeners = selectListeners(providers);
for (ICListenerAgent listener : listeners) {
if (!isListenerInTheListOfAssociations(associations, listener)) {
associations.add(new ListenerAssociation(listener, cfgDescription));
}
}
}
}
}
return associations;
}
/**
* Unregister listeners which are not used anymore and register new listeners.
* The method is called when project description is applied to workspace.
*
* @param oldPrjDescription - old project descriptions being replaced in the workspace.
* @param newPrjDescription - new project description being applied to the workspace.
*/
public static void reRegisterListeners(ICProjectDescription oldPrjDescription, ICProjectDescription newPrjDescription) {
if (oldPrjDescription == newPrjDescription) {
return;
}
List<ICListenerAgent> oldListeners = getListeners(oldPrjDescription);
List<ListenerAssociation> newAssociations = getListenersAssociations(newPrjDescription);
// unregister old listeners
for (ICListenerAgent oldListener : oldListeners) {
if (!isListenerInTheListOfAssociations(newAssociations, oldListener)) {
int count = 0;
if (oldListener instanceof LanguageSettingsWorkspaceProvider) {
count = ((LanguageSettingsWorkspaceProvider) oldListener).decrementProjectCount();
}
if (count <= 0) {
try {
oldListener.unregisterListener();
} catch (Throwable e) {
// protect from any exceptions from implementers
CCorePlugin.log("Exception trying unregister listener "+((ILanguageSettingsProvider) oldListener).getId(), e); //$NON-NLS-1$
}
}
}
}
// register new listeners
for (ListenerAssociation newListenerAssociation : newAssociations) {
ICListenerAgent newListener = newListenerAssociation.listener;
if (!isInList(oldListeners, newListener)) {
int count = 1;
if (newListener instanceof LanguageSettingsWorkspaceProvider) {
count = ((LanguageSettingsWorkspaceProvider) newListener).incrementProjectCount();
}
if (count == 1) {
try {
newListener.registerListener(newListenerAssociation.cfgDescription);
} catch (Throwable e) {
// protect from any exceptions from implementers
CCorePlugin.log("Exception trying register listener "+((ILanguageSettingsProvider) newListener).getId(), e); //$NON-NLS-1$
}
}
}
}
}
/**
* Adds a listener that will be notified of changes in language settings.
*
* @param listener - the listener to add
*/
public static void registerLanguageSettingsChangeListener(ILanguageSettingsChangeListener listener) {
fLanguageSettingsChangeListeners.add(listener);
}
/**
* Removes a language settings change listener.
*
* @param listener - the listener to remove.
*/
public static void unregisterLanguageSettingsChangeListener(ILanguageSettingsChangeListener listener) {
fLanguageSettingsChangeListeners.remove(listener);
}
/**
* Notifies all language settings change listeners of a change in language settings entries.
*
* @param event - the {@link ILanguageSettingsChangeEvent} event to be broadcast.
*/
private static void notifyLanguageSettingsChangeListeners(ILanguageSettingsChangeEvent event) {
for (Object listener : fLanguageSettingsChangeListeners.getListeners()) {
((ILanguageSettingsChangeListener) listener).handleEvent(event);
}
}
/**
* Get list of setting entries from the pool in {@link LanguageSettingsStorage}.
*/
private static List<ICLanguageSettingEntry> getSettingEntriesPooled(ILanguageSettingsProvider provider,
ICConfigurationDescription cfgDescription, IResource rc, String languageId) {
try {
return LanguageSettingsStorage.getPooledList(provider.getSettingEntries(cfgDescription, rc, languageId));
} catch (Throwable e) {
String cfgId = cfgDescription!=null ? cfgDescription.getId() : null;
@SuppressWarnings("nls")
String msg = "Exception in provider "+provider.getId()+": getSettingEntries("+cfgId+", "+rc+", "+languageId+")";
CCorePlugin.log(msg, e);
// return empty list to prevent getting potentially non-empty list from up the resource tree
return LanguageSettingsStorage.getPooledEmptyList();
}
}
/**
* Returns the list of setting entries of the given provider
* for the given configuration description, resource and language.
* This method reaches to the parent folder of the resource recursively
* if the resource does not define the entries for the given provider.
*
* @param provider - language settings provider.
* @param cfgDescription - configuration description.
* @param rc - resource such as file or folder.
* @param languageId - language id.
*
* @return the list of setting entries which is unmodifiable. Never returns {@code null}
* although individual providers mandated to return {@code null} if no settings defined.
*/
public static List<ICLanguageSettingEntry> getSettingEntriesUpResourceTree(ILanguageSettingsProvider provider, ICConfigurationDescription cfgDescription, IResource rc, String languageId) {
Assert.isTrue( !(rc instanceof IWorkspaceRoot) );
if (provider != null) {
List<ICLanguageSettingEntry> entries = getSettingEntriesPooled(provider, cfgDescription, rc, languageId);
if (entries != null) {
return entries;
}
if (rc != null) {
IResource parentFolder = (rc instanceof IProject) ? null : rc.getParent();
if (parentFolder != null) {
return getSettingEntriesUpResourceTree(provider, cfgDescription, parentFolder, languageId);
}
// if out of parent resources - get default entries
entries = getSettingEntriesPooled(provider, null, null, languageId);
if (entries != null) {
return entries;
}
}
}
return LanguageSettingsStorage.getPooledEmptyList();
}
/**
* Test if the binary flag contains a particular bit.
*/
private static boolean checkBit(int flags, int bit) {
return (flags & bit) == bit;
}
/**
* Returns the list of setting entries of a certain kind (such as include paths)
* for the given configuration description, resource and language. This is a
* combined list for all providers taking into account settings of parent folder
* if settings for the given resource are not defined.
*
* @param cfgDescription - configuration description.
* @param rc - resource such as file or folder.
* @param languageId - language id.
* @param kind - kind of language settings entries, such as
* {@link ICSettingEntry#INCLUDE_PATH} etc. This is a binary flag
* and it is possible to specify composite kind.
* Use {@link ICSettingEntry#ALL} to get all kinds.
* @param checkLocality - specifies if parameter {@code isLocal} should be considered.
* @param isLocal - {@code true} if "local" entries should be provided and
* {@code false} for "system" entries. This makes sense for include paths where
* [#include "..."] is "local" and [#include <...>] is system.
*
* @return the list of setting entries found.
*/
private static List<ICLanguageSettingEntry> getSettingEntriesByKind(ICConfigurationDescription cfgDescription,
IResource rc, String languageId, int kind, boolean checkLocality, boolean isLocal) {
if (!(cfgDescription instanceof ILanguageSettingsProvidersKeeper)) {
return null;
}
List<ICLanguageSettingEntry> entries = new ArrayList<ICLanguageSettingEntry>();
List<String> alreadyAdded = new ArrayList<String>();
List<ILanguageSettingsProvider> providers = ((ILanguageSettingsProvidersKeeper) cfgDescription).getLanguageSettingProviders();
for (ILanguageSettingsProvider provider: providers) {
List<ICLanguageSettingEntry> providerEntries = getSettingEntriesUpResourceTree(provider, cfgDescription, rc, languageId);
for (ICLanguageSettingEntry entry : providerEntries) {
if (entry != null) {
String entryName = entry.getName();
boolean isRightKind = checkBit(kind, entry.getKind());
// Only first entry is considered
// Entry flagged as "UNDEFINED" prevents adding entry with the same name down the line
if (isRightKind && !alreadyAdded.contains(entryName)) {
int flags = entry.getFlags();
boolean isRightLocal = !checkLocality || (checkBit(flags, ICSettingEntry.LOCAL) == isLocal);
if (isRightLocal) {
if (!checkBit(flags, ICSettingEntry.UNDEFINED)) {
entries.add(entry);
}
alreadyAdded.add(entryName);
}
}
}
}
}
return entries;
}
/**
* Returns the list of setting entries of a certain kind (such as include paths)
* for the given configuration description, resource and language. This is a
* combined list for all providers taking into account settings of parent folder
* if settings for the given resource are not defined. For include paths both
* local (#include "...") and system (#include <...>) entries are returned.
*
* @param cfgDescription - configuration description.
* @param rc - resource such as file or folder.
* @param languageId - language id.
* @param kind - kind of language settings entries, such as
* {@link ICSettingEntry#INCLUDE_PATH} etc. This is a binary flag
* and it is possible to specify composite kind.
* Use {@link ICSettingEntry#ALL} to get all kinds.
*
* @return the list of setting entries.
*/
public static List<ICLanguageSettingEntry> getSettingEntriesByKind(ICConfigurationDescription cfgDescription, IResource rc, String languageId, int kind) {
return getSettingEntriesByKind(cfgDescription, rc, languageId, kind, /* checkLocality */ false, /* isLocal */ false);
}
/**
* Returns the list of "system" (such as [#include <...>]) setting entries of a certain kind
* for the given configuration description, resource and language. This is a
* combined list for all providers taking into account settings of parent folder
* if settings for the given resource are not defined.
*
* @param cfgDescription - configuration description.
* @param rc - resource such as file or folder.
* @param languageId - language id.
* @param kind - kind of language settings entries, such as
* {@link ICSettingEntry#INCLUDE_PATH} etc. This is a binary flag
* and it is possible to specify composite kind.
* Use {@link ICSettingEntry#ALL} to get all kinds.
*
* @return the list of setting entries.
*/
public static List<ICLanguageSettingEntry> getSystemSettingEntriesByKind(ICConfigurationDescription cfgDescription, IResource rc, String languageId, int kind) {
return getSettingEntriesByKind(cfgDescription, rc, languageId, kind, /* checkLocality */ true, /* isLocal */ false);
}
/**
* Returns the list of "local" (such as [#include "..."]) setting entries of a certain kind
* for the given configuration description, resource and language. This is a
* combined list for all providers taking into account settings of parent folder
* if settings for the given resource are not defined.
*
* @param cfgDescription - configuration description.
* @param rc - resource such as file or folder.
* @param languageId - language id.
* @param kind - kind of language settings entries, such as
* {@link ICSettingEntry#INCLUDE_PATH} etc. This is a binary flag
* and it is possible to specify composite kind.
* Use {@link ICSettingEntry#ALL} to get all kinds.
*
* @return the list of setting entries.
*/
public static List<ICLanguageSettingEntry> getLocalSettingEntriesByKind(ICConfigurationDescription cfgDescription, IResource rc, String languageId, int kind) {
return getSettingEntriesByKind(cfgDescription, rc, languageId, kind, /* checkLocality */ true, /* isLocal */ true);
}
/**
* Deep clone of a list of language settings providers.
*
* @param baseProviders - list of providers to clone.
* @return newly cloned list.
*/
public static List<ILanguageSettingsProvider> cloneProviders(List<ILanguageSettingsProvider> baseProviders) {
List<ILanguageSettingsProvider> newProviders = new ArrayList<ILanguageSettingsProvider>();
for (ILanguageSettingsProvider provider : baseProviders) {
if (provider instanceof ILanguageSettingsEditableProvider) {
ILanguageSettingsEditableProvider newProvider = LanguageSettingsManager.getProviderCopy((ILanguageSettingsEditableProvider) provider, true);
if (newProvider != null) {
provider = newProvider;
}
}
newProviders.add(provider);
}
return new ArrayList<ILanguageSettingsProvider>(newProviders);
}
}