/*******************************************************************************
* Copyright (c) 2009, 2014 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.core.language.settings.providers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
import org.eclipse.cdt.internal.core.XmlUtil;
import org.eclipse.cdt.internal.core.language.settings.providers.LanguageSettingsProvidersSerializer;
import org.eclipse.cdt.internal.core.language.settings.providers.LanguageSettingsSerializableStorage;
import org.eclipse.cdt.internal.core.settings.model.CConfigurationSpecSettings;
import org.eclipse.cdt.internal.core.settings.model.IInternalCCfgInfo;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* This class is the base class for language settings providers able to serialize
* into XML storage.
* Although this class has setter methods, by design its instances are not editable in UI
* nor instances can be assigned to a configuration (cannot be global or non-shared).
* Implement {@link ILanguageSettingsEditableProvider} interface for that. There is a generic
* implementation of this interface available to be used, see {@link LanguageSettingsGenericProvider}.
*
* For more on the suggested way of extending this class see the description of
* {@link ILanguageSettingsProvider}.
*
* @since 5.4
*/
public class LanguageSettingsSerializableProvider extends LanguageSettingsBaseProvider implements ILanguageSettingsBroadcastingProvider {
protected static final String ATTR_ID = LanguageSettingsProvidersSerializer.ATTR_ID;
protected static final String ATTR_NAME = LanguageSettingsProvidersSerializer.ATTR_NAME;
protected static final String ATTR_CLASS = LanguageSettingsProvidersSerializer.ATTR_CLASS;
protected static final String ELEM_PROVIDER = LanguageSettingsProvidersSerializer.ELEM_PROVIDER;
protected static final String ELEM_LANGUAGE_SCOPE = LanguageSettingsProvidersSerializer.ELEM_LANGUAGE_SCOPE;
private LanguageSettingsSerializableStorage fStorage = new LanguageSettingsSerializableStorage();
/**
* Default constructor. This constructor has to be always followed with setting id and name of the provider.
* This constructor is necessary to instantiate the class via the extension point in plugin.xml.
*/
public LanguageSettingsSerializableProvider() {
super();
}
/**
* Constructor.
*
* @param id - id of the provider.
* @param name - name of the provider. Note that this name shows up in UI.
*/
public LanguageSettingsSerializableProvider(String id, String name) {
super(id, name);
}
/**
* Constructor which allows to instantiate provider defined via XML markup.
*
* @param elementProvider
*/
public LanguageSettingsSerializableProvider(Element elementProvider) {
super();
load(elementProvider);
}
@Override
public void configureProvider(String id, String name, List<String> languages, List<ICLanguageSettingEntry> entries, Map<String, String> properties) {
// do not pass entries to super, keep them in local storage
super.configureProvider(id, name, languages, null, properties);
fStorage.clear();
if (entries!=null) {
// note that these entries are intended to be retrieved by LanguageSettingsManager.getSettingEntriesUpResourceTree()
// when the whole resource hierarchy has been traversed up
setSettingEntries(null, null, null, entries);
}
}
/**
* @return {@code true} if the provider does not keep any settings yet or {@code false} if there are some.
*/
public boolean isEmpty() {
return fStorage.isEmpty();
}
/**
* Sets the language scope of the provider.
*
* @param languages - the list of languages this provider provides for.
* If {@code null}, the provider provides for any language.
*
* @see #getLanguageScope()
*/
public void setLanguageScope(List<String> languages) {
if (languages == null) {
this.languageScope = null;
} else {
this.languageScope = new ArrayList<>(languages);
}
}
/**
* Clears all the entries for all configurations, all resources and all languages.
*/
public void clear() {
fStorage.clear();
}
/**
* Sets language settings entries for the provider.
* Note that the entries are not persisted at that point. Use this method to set
* the entries for all resources one by one and after all done persist in one shot
* using {@link #serializeLanguageSettings(ICConfigurationDescription)}.
* See for example {@code AbstractBuildCommandParser} and {@code AbstractBuiltinSpecsDetector}
* in build plugins.
*
* @param cfgDescription - configuration description.
* @param rc - resource such as file or folder. If {@code null} the entries are
* considered to be being defined as default entries for resources.
* @param languageId - language id. If {@code null}, then entries are considered
* to be defined for the language scope. See {@link #getLanguageScope()}
* @param entries - language settings entries to set.
*/
public void setSettingEntries(ICConfigurationDescription cfgDescription, IResource rc, String languageId,
List<? extends ICLanguageSettingEntry> entries) {
String rcProjectPath = rc!=null ? rc.getProjectRelativePath().toString() : null;
fStorage.setSettingEntries(rcProjectPath, languageId, entries);
}
/**
* {@inheritDoc}
* <br>
* Note that this list is <b>unmodifiable</b>. To modify the list copy it, change and use
* {@link #setSettingEntries(ICConfigurationDescription, IResource, String, List)}.
* <br><br>
* Note also that <b>you can compare these lists with simple equality operator ==</b>,
* as the lists themselves are backed by WeakHashSet<List<ICLanguageSettingEntry>> where
* identical copies (deep comparison is used) are replaced with the same one instance.
*/
@Override
public List<ICLanguageSettingEntry> getSettingEntries(ICConfigurationDescription cfgDescription, IResource rc, String languageId) {
String rcProjectPath = rc!=null ? rc.getProjectRelativePath().toString() : null;
List<ICLanguageSettingEntry> entries = fStorage.getSettingEntries(rcProjectPath, languageId);
if (entries == null) {
if (languageId != null && (languageScope == null || languageScope.contains(languageId))) {
entries = fStorage.getSettingEntries(rcProjectPath, null);
}
}
return entries;
}
/**
* Serialize the provider under parent XML element.
* This is convenience method not intended to be overridden on purpose.
* Override {@link #serializeAttributes(Element)} or
* {@link #serializeEntries(Element)} instead.
*
* @param parentElement - element where to serialize.
* @return - newly created "provider" element. That element will already be
* attached to the parent element.
*/
final public Element serialize(Element parentElement) {
/*
<provider id="provider.id" ...>
<language-scope id="lang.id"/>
<language id="lang.id">
<resource project-relative-path="/">
<entry flags="" kind="includePath" name="path"/>
</resource>
</language>
</provider>
*/
Element elementProvider = serializeAttributes(parentElement);
serializeEntries(elementProvider);
return elementProvider;
}
/**
* Serialize the provider attributes under parent XML element. That is
* equivalent to serializing everything (including language scope) except entries.
*
* @param parentElement - element where to serialize.
* @return - newly created "provider" element. That element will already be
* attached to the parent element.
*/
public Element serializeAttributes(Element parentElement) {
// Keeps pairs: key, value. See JavaDoc XmlUtil.appendElement(Node, String, String[]).
List<String> attributes = new ArrayList<String>();
attributes.add(ATTR_ID);
attributes.add(getId());
attributes.add(ATTR_NAME);
attributes.add(getName());
attributes.add(ATTR_CLASS);
attributes.add(getClass().getCanonicalName());
for (Entry<String, String> entry : properties.entrySet()) {
attributes.add(entry.getKey());
attributes.add(entry.getValue());
}
Element elementProvider = XmlUtil.appendElement(parentElement, ELEM_PROVIDER, attributes.toArray(new String[0]));
if (languageScope!=null) {
for (String langId : languageScope) {
XmlUtil.appendElement(elementProvider, ELEM_LANGUAGE_SCOPE, new String[] {ATTR_ID, langId});
}
}
return elementProvider;
}
/**
* Serialize the provider entries under parent XML element.
* @param elementProvider - element where to serialize the entries.
*/
public void serializeEntries(Element elementProvider) {
fStorage.serializeEntries(elementProvider);
}
/**
* Convenience method to persist language settings entries for the project or
* workspace as often-used operation.
* Note that configuration description is passed as an argument but the
* current implementation saves all configurations.
*
* @param cfgDescription - configuration description.
* If not {@code null}, all providers of the project are serialized.
* If {@code null}, global workspace providers are serialized.
*
* @return - status of operation.
*/
public IStatus serializeLanguageSettings(ICConfigurationDescription cfgDescription) {
IStatus status = Status.OK_STATUS;
try {
if (cfgDescription != null) {
LanguageSettingsManager.serializeLanguageSettings(cfgDescription.getProjectDescription());
} else {
LanguageSettingsManager.serializeLanguageSettingsWorkspace();
}
} catch (CoreException e) {
status = new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, IStatus.ERROR, "Error serializing language settings", e); //$NON-NLS-1$
CCorePlugin.log(status);
}
return status;
}
/**
* Convenience method to persist language settings entries in background for the project or
* workspace as often-used operation.
* Note that configuration description is passed as an argument but the
* current implementation saves all configurations.
*
* @param cfgDescription - configuration description.
* If not {@code null}, all providers of the project are serialized.
* If {@code null}, global workspace providers are serialized.
*/
public void serializeLanguageSettingsInBackground(ICConfigurationDescription cfgDescription) {
if (cfgDescription != null) {
if (isLanguageSettingsProviderStoreChanged(cfgDescription)) {
LanguageSettingsManager.serializeLanguageSettingsInBackground(cfgDescription.getProjectDescription());
}
} else {
LanguageSettingsManager.serializeLanguageSettingsWorkspaceInBackground();
}
}
/**
* Compare provider store with cached persistent store used to calculate delta.
*/
private boolean isLanguageSettingsProviderStoreChanged(ICConfigurationDescription cfgDescription) {
if (cfgDescription instanceof IInternalCCfgInfo) {
try {
CConfigurationSpecSettings ss = ((IInternalCCfgInfo)cfgDescription).getSpecSettings();
if (ss != null) {
return ss.isLanguageSettingsProviderStoreChanged(this);
}
} catch (CoreException e) {
CCorePlugin.log(e);
}
}
// If something went wrong assuming it might have changed
return true;
}
/**
* Load provider from XML provider element.
* This is convenience method not intended to be overridden on purpose.
* Override {@link #loadAttributes(Element)} or
* {@link #loadEntries(Element)} instead.
*
* @param providerNode - XML element "provider" to load provider from.
*/
final public void load(Element providerNode) {
fStorage.clear();
languageScope = null;
// provider/configuration/language/resource/entry
if (providerNode!=null) {
loadAttributes(providerNode);
loadEntries(providerNode);
}
}
/**
* Determine and set language scope from given XML node.
*/
private void loadLanguageScopeElement(Node parentNode) {
if (languageScope==null) {
languageScope = new ArrayList<String>();
}
String id = XmlUtil.determineAttributeValue(parentNode, ATTR_ID);
languageScope.add(id);
}
/**
* Load attributes from XML provider element.
* @param providerNode - XML element "provider" to load attributes from.
*/
public void loadAttributes(Element providerNode) {
String providerId = XmlUtil.determineAttributeValue(providerNode, ATTR_ID);
String providerName = XmlUtil.determineAttributeValue(providerNode, ATTR_NAME);
properties.clear();
NamedNodeMap attrs = providerNode.getAttributes();
for (int i=0; i<attrs.getLength(); i++) {
Node attr = attrs.item(i);
if (attr.getNodeType()==Node.ATTRIBUTE_NODE) {
String key = attr.getNodeName();
if (!key.equals(ATTR_ID) && !key.equals(ATTR_NAME) && !key.equals(ATTR_CLASS)) {
String value = attr.getNodeValue();
properties.put(key, value);
}
}
}
this.setId(providerId);
this.setName(providerName);
NodeList nodes = providerNode.getChildNodes();
for (int i=0;i<nodes.getLength();i++) {
Node elementNode = nodes.item(i);
if(elementNode.getNodeType() != Node.ELEMENT_NODE)
continue;
if (ELEM_LANGUAGE_SCOPE.equals(elementNode.getNodeName())) {
loadLanguageScopeElement(elementNode);
}
}
}
/**
* Load provider entries from XML provider element.
* @param providerNode - parent XML element "provider" where entries are defined.
*/
public void loadEntries(Element providerNode) {
fStorage.loadEntries(providerNode);
}
/**
* Set a custom property of the provider.
* <br><br>
* A note of caution - do not use default values for a provider which are different
* from empty or {@code null} value. When providers are checked for equality
* (during internal operations in core) the missing properties are evaluated as
* empty ones.
*
* @see LanguageSettingsBaseProvider#getProperty(String)
*
* @param key - name of the property.
* @param value - value of the property.
* If value is {@code null} the property is removed from the list.
*/
public void setProperty(String key, String value) {
properties.put(key, value);
}
/**
* Set a custom boolean property of the provider.
* <br>Please, note that default value is always {@code false}.
* @see LanguageSettingsBaseProvider#getProperty(String)
*
* @param key - name of the property.
* @param value - {@code boolean} value of the property.
*/
public void setPropertyBool(String key, boolean value) {
properties.put(key, Boolean.toString(value));
}
/**
* See {@link #cloneShallow()}. This method is extracted to avoid expressing
* {@link #clone()} via {@link #cloneShallow()}. Do not inline to "optimize"!
*/
private LanguageSettingsSerializableProvider cloneShallowInternal() throws CloneNotSupportedException {
LanguageSettingsSerializableProvider clone = (LanguageSettingsSerializableProvider)super.clone();
if (languageScope!=null)
clone.languageScope = new ArrayList<String>(languageScope);
clone.properties = new HashMap<String, String>(properties);
clone.fStorage = new LanguageSettingsSerializableStorage();
return clone;
}
/**
* Shallow clone of the provider. "Shallow" is defined here as the exact copy except that
* the copy will have zero language settings entries.
*
* @return shallow copy of the provider.
* @throws CloneNotSupportedException in case {@link #clone()} throws the exception.
*/
protected LanguageSettingsSerializableProvider cloneShallow() throws CloneNotSupportedException {
return cloneShallowInternal();
}
@Override
protected LanguageSettingsSerializableProvider clone() throws CloneNotSupportedException {
LanguageSettingsSerializableProvider clone = cloneShallowInternal();
clone.fStorage = fStorage.clone();
return clone;
}
@Override
public LanguageSettingsStorage copyStorage() {
try {
return fStorage.clone();
} catch (CloneNotSupportedException e) {
CCorePlugin.log(e);
}
return null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((fStorage == null) ? 0 : fStorage.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
LanguageSettingsSerializableProvider other = (LanguageSettingsSerializableProvider) obj;
if (fStorage == null) {
if (other.fStorage != null)
return false;
} else if (!fStorage.equals(other.fStorage))
return false;
return true;
}
}