/******************************************************************************* * 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.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.cdt.core.AbstractExecutableExtensionBase; import org.eclipse.cdt.core.CCorePlugin; 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.LanguageSettingsBaseProvider; import org.eclipse.cdt.core.language.settings.providers.LanguageSettingsGenericProvider; import org.eclipse.cdt.core.language.settings.providers.LanguageSettingsSerializableProvider; import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry; import org.eclipse.cdt.core.settings.model.util.CDataUtil; import org.eclipse.cdt.core.settings.model.util.LanguageSettingEntriesSerializer; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; /** * Class {@code LanguageSettingsExtensionManager} manages {@link ILanguageSettingsProvider} extensions */ public class LanguageSettingsExtensionManager { /** Name of the extension point for contributing language settings */ static final String PROVIDER_EXTENSION_POINT_ID = "org.eclipse.cdt.core.LanguageSettingsProvider"; //$NON-NLS-1$ static final String PROVIDER_EXTENSION_SIMPLE_ID = "LanguageSettingsProvider"; //$NON-NLS-1$ static final String ATTR_ID = "id"; //$NON-NLS-1$ static final String ATTR_NAME = "name"; //$NON-NLS-1$ static final String ATTR_CLASS = "class"; //$NON-NLS-1$ static final String ATTR_PREFER_NON_SHARED = "prefer-non-shared"; //$NON-NLS-1$ static final String ELEM_PROVIDER = "provider"; //$NON-NLS-1$ static final String ELEM_LANGUAGE_SCOPE = "language-scope"; //$NON-NLS-1$ static final String ELEM_ENTRY = "entry"; //$NON-NLS-1$ static final String ATTR_ENTRY_NAME = "name"; //$NON-NLS-1$ static final String ATTR_ENTRY_KIND = "kind"; //$NON-NLS-1$ static final String ATTR_ENTRY_VALUE = "value"; //$NON-NLS-1$ static final String ELEM_ENTRY_FLAG = "flag"; //$NON-NLS-1$ /** * Extension providers loaded once and used for equality only. * Those who request extension provider will get copy rather than real instance. */ private static final LinkedHashMap<String, ILanguageSettingsProvider> fExtensionProviders = new LinkedHashMap<String, ILanguageSettingsProvider>(); /** * Providers loaded initially via static initializer. */ static { try { loadProviderExtensions(); } catch (Throwable e) { CCorePlugin.log("Error loading language settings providers extensions", e); //$NON-NLS-1$ } } /** * Load language settings providers contributed via the extension point. */ synchronized private static void loadProviderExtensions() { List<ILanguageSettingsProvider> providers = new ArrayList<ILanguageSettingsProvider>(); loadProviderExtensions(Platform.getExtensionRegistry(), providers); // sort by name - the providers defined via extensions are kept in separate list sorted by name Collections.sort(providers, new Comparator<ILanguageSettingsProvider>() { @Override public int compare(ILanguageSettingsProvider pr1, ILanguageSettingsProvider pr2) { return pr1.getName().compareTo(pr2.getName()); } } ); fExtensionProviders.clear(); for (ILanguageSettingsProvider provider : providers) { fExtensionProviders.put(provider.getId(), provider); } } /** * Load contributed extensions from extension registry. * * @param registry - extension registry * @param providers - resulting set of providers */ private static void loadProviderExtensions(IExtensionRegistry registry, Collection<ILanguageSettingsProvider> providers) { providers.clear(); IExtensionPoint extension = registry.getExtensionPoint(CCorePlugin.PLUGIN_ID, PROVIDER_EXTENSION_SIMPLE_ID); if (extension != null) { IExtension[] extensions = extension.getExtensions(); for (IExtension ext : extensions) { for (IConfigurationElement cfgEl : ext.getConfigurationElements()) { ILanguageSettingsProvider provider = null; String id = null; try { if (cfgEl.getName().equals(ELEM_PROVIDER)) { id = determineAttributeValue(cfgEl, ATTR_ID); provider = createExecutableExtension(cfgEl); configureExecutableProvider(provider, cfgEl); providers.add(provider); } } catch (Throwable e) { CCorePlugin.log("Cannot load LanguageSettingsProvider extension id=" + id, e); //$NON-NLS-1$ } } } } } private static String determineAttributeValue(IConfigurationElement ce, String attr) { String value = ce.getAttribute(attr); return value != null ? value : ""; //$NON-NLS-1$ } /** * Creates empty non-configured provider as executable extension from extension point definition. * If "class" attribute is empty {@link LanguageSettingsBaseProvider} is created. * * @param ce - configuration element with provider definition * @return new non-configured provider * @throws CoreException in case of failure */ private static ILanguageSettingsProvider createExecutableExtension(IConfigurationElement ce) throws CoreException { String ceClass = ce.getAttribute(ATTR_CLASS); ILanguageSettingsProvider provider = null; if (ceClass==null || ceClass.trim().length()==0 || ceClass.equals(LanguageSettingsBaseProvider.class.getCanonicalName())) { provider = new LanguageSettingsBaseProvider(); } else { provider = (ILanguageSettingsProvider)ce.createExecutableExtension(ATTR_CLASS); } return provider; } /** * Configure language settings provider with parameters defined in XML metadata. * * @param provider - empty non-configured provider. * @param ce - configuration element from registry representing XML. */ private static void configureExecutableProvider(ILanguageSettingsProvider provider, IConfigurationElement ce) { String ceId = determineAttributeValue(ce, ATTR_ID); String ceName = determineAttributeValue(ce, ATTR_NAME); Map<String, String> ceAttributes = new HashMap<String, String>(); List<String> languages = null; List<ICLanguageSettingEntry> entries = null; for (String attr : ce.getAttributeNames()) { if (!attr.equals(ATTR_ID) && !attr.equals(ATTR_NAME) && !attr.equals(ATTR_CLASS)) { ceAttributes.put(attr, determineAttributeValue(ce, attr)); } } for (IConfigurationElement ceLang : ce.getChildren(ELEM_LANGUAGE_SCOPE)) { String langId = determineAttributeValue(ceLang, ATTR_ID); if (langId.length() > 0) { if (languages == null) { languages = new ArrayList<String>(); } languages.add(langId); } } for (IConfigurationElement ceEntry : ce.getChildren(ELEM_ENTRY)) { try { int entryKind = LanguageSettingEntriesSerializer.stringToKind(determineAttributeValue(ceEntry, ATTR_ENTRY_KIND)); String entryName = determineAttributeValue(ceEntry, ATTR_ENTRY_NAME); String entryValue = determineAttributeValue(ceEntry, ATTR_ENTRY_VALUE); int flags = 0; for (IConfigurationElement ceFlags : ceEntry.getChildren(ELEM_ENTRY_FLAG)) { int bitFlag = LanguageSettingEntriesSerializer.composeFlags(determineAttributeValue(ceFlags, ATTR_ENTRY_VALUE)); flags |= bitFlag; } ICLanguageSettingEntry entry = (ICLanguageSettingEntry) CDataUtil.createEntry( entryKind, entryName, entryValue, null, flags); if (entries == null) { entries = new ArrayList<ICLanguageSettingEntry>(); } entries.add(entry); } catch (Exception e) { CCorePlugin.log("Error creating language settings entry ", e); //$NON-NLS-1$ } } if (provider instanceof LanguageSettingsBaseProvider) { ((LanguageSettingsBaseProvider) provider).configureProvider(ceId, ceName, languages, entries, ceAttributes); } else if (provider instanceof AbstractExecutableExtensionBase) { ((AbstractExecutableExtensionBase) provider).setId(ceId); ((AbstractExecutableExtensionBase) provider).setName(ceName); } } /** * Creates provider from extension point definition which matches value of the given attribute. * The method will inspect extension registry for extension point "org.eclipse.cdt.core.LanguageSettingsProvider" * to determine bundle and instantiate the class. * * @param attr - attribute to match. * @param attrValue - value of the attribute to match. * @param registry - extension registry. * @param configure - flag which indicates if provider needs to be configured. * @return new instance of the provider */ private static ILanguageSettingsProvider loadProviderFromRegistry(String attr, String attrValue, IExtensionRegistry registry, boolean configure) { try { IExtensionPoint extension = registry.getExtensionPoint(CCorePlugin.PLUGIN_ID, PROVIDER_EXTENSION_SIMPLE_ID); if (extension != null) { IExtension[] extensions = extension.getExtensions(); for (IExtension ext : extensions) { for (IConfigurationElement cfgEl : ext.getConfigurationElements()) { if (cfgEl.getName().equals(ELEM_PROVIDER) && attrValue.equals(cfgEl.getAttribute(attr))) { ILanguageSettingsProvider provider = createExecutableExtension(cfgEl); if (configure) { configureExecutableProvider(provider, cfgEl); } return provider; } } } } } catch (Exception e) { CCorePlugin.log("Error creating language settings provider.", e); //$NON-NLS-1$ } return null; } /** * Create an instance of non-configured language settings provider of given class name. * The class should be known to this method or registered with the extension point. * * @param className - class name to instantiate. * @return new instance of language settings provider. * * @throws CoreException if not able to create a new instance. */ /*package*/ static ILanguageSettingsProvider instantiateProviderClass(String className) throws CoreException { if (className==null || className.equals(LanguageSettingsSerializableProvider.class.getName())) { return new LanguageSettingsSerializableProvider(); } if (className.equals(LanguageSettingsGenericProvider.class.getName())) { return new LanguageSettingsGenericProvider(); } // Create it as executable extension from the extension registry. ILanguageSettingsProvider provider = loadProviderFromRegistry(ATTR_CLASS, className, Platform.getExtensionRegistry(), false); if (provider == null) { String msg = "Not able to load provider class=" + className; //$NON-NLS-1$ throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, msg)); } return provider; } /** * Load an instance of language settings provider of given id from the extension point. * The class should be registered with the extension point. * * @param id - class name to instantiate. * @return new instance of language settings provider. */ /*package*/ static ILanguageSettingsProvider loadProvider(String id) { if (id==null) { return null; } // Create it as executable extension from the extension registry. ILanguageSettingsProvider provider = loadProviderFromRegistry(ATTR_ID, id, Platform.getExtensionRegistry(), true); if (provider == null) { String msg = "Not able to load provider id=" + id; //$NON-NLS-1$ CCorePlugin.log(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, msg, new Exception(msg))); } return provider; } /** * Returns list of provider id-s contributed by all extensions. * @return the provider id-s. */ public static Set<String> getExtensionProviderIds() { return fExtensionProviders.keySet(); } /** * Copy language settings provider. It is different from clone() methods in that * it does not throw {@code CloneNotSupportedException} but returns {@code null} * instead. * * @param provider - language settings provider to copy. * @param deep - {@code true} to request deep copy including copying settings entries * or {@code false} to return shallow copy with no settings entries. * * @return a copy of the provider or {@code null} if copying is not possible. */ public static ILanguageSettingsEditableProvider getProviderCopy(ILanguageSettingsEditableProvider provider, boolean deep) { try { if (deep) { return provider.clone(); } else { return provider.cloneShallow(); } } catch (CloneNotSupportedException e) { CCorePlugin.log("Error cloning provider " + provider.getId() + ", class " + provider.getClass(), e); //$NON-NLS-1$ //$NON-NLS-2$ } return null; } /** * Get language settings provider defined via extension point * {@code org.eclipse.cdt.core.LanguageSettingsProvider}. * A new copy of the extension provider is returned. * * @param id - ID of the extension provider. * @param deep - {@code true} to request deep copy including copying settings entries * or {@code false} to return shallow copy with no settings entries. * @return the copy of the extension provider if possible (i.e. for {@link ILanguageSettingsEditableProvider}) * or {@code null} if provider is not copyable. */ public static ILanguageSettingsProvider getExtensionProviderCopy(String id, boolean deep) { ILanguageSettingsProvider provider = fExtensionProviders.get(id); if (provider instanceof ILanguageSettingsEditableProvider) { return getProviderCopy((ILanguageSettingsEditableProvider) provider, deep); } return null; } /** * Test if the provider is equal to the one defined via extension point * {@code org.eclipse.cdt.core.LanguageSettingsProvider}. * * @param provider - the provider to test. * @param deep - {@code true} to check for deep equality testing also settings entries * or {@code false} to test shallow copy with no settings entries. * Shallow equality is applicable only for {@link ILanguageSettingsEditableProvider}. * @return - {@code true} if the provider matches the extension or {@code false} otherwise. */ public static boolean isEqualExtensionProvider(ILanguageSettingsProvider provider, boolean deep) { String id = provider.getId(); if (deep || !(provider instanceof ILanguageSettingsEditableProvider)) { ILanguageSettingsProvider extensionProvider = fExtensionProviders.get(id); return provider.equals(extensionProvider); } else { ILanguageSettingsEditableProvider providerShallow = getProviderCopy((ILanguageSettingsEditableProvider) provider, false); ILanguageSettingsProvider extensionProviderShallow = getExtensionProviderCopy(id, false); return providerShallow == extensionProviderShallow || (providerShallow != null && providerShallow.equals(extensionProviderShallow)); } } /** * Tells if the provider is meant to be shared between projects in workspace * or belong to a specific configuration. This attribute is defined in * {@code org.eclipse.cdt.core.LanguageSettingsProvider} extension point. * <br>Note that only {@link ILanguageSettingsEditableProvider} can be owned by * a configuration. * * @param id - ID of the provider to inquire. * @return {@code true} if the provider is designed to be shared, * {@code false} if configuration-owned. */ public static boolean isPreferShared(String id) { ILanguageSettingsProvider provider = fExtensionProviders.get(id); if (provider instanceof LanguageSettingsBaseProvider && provider instanceof ILanguageSettingsEditableProvider) { return ! ((LanguageSettingsBaseProvider) provider).getPropertyBool(ATTR_PREFER_NON_SHARED); } return true; } }