/*******************************************************************************
* Copyright (c) 2007, 2016 Symbian Software Systems 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 Ferguson (Symbian) - Initial implementation
* Markus Schorn (Wind River Systems)
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.internal.core.index.provider;
import java.util.ArrayList;
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.index.IIndexManager;
import org.eclipse.cdt.core.index.provider.IIndexProvider;
import org.eclipse.cdt.core.index.provider.IReadOnlyPDOMProvider;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ElementChangedEvent;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICElementDelta;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IElementChangedListener;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.internal.core.index.IIndexFragment;
import org.eclipse.cdt.internal.core.pdom.PDOM;
import org.eclipse.core.resources.IProject;
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;
import org.eclipse.osgi.service.resolver.VersionRange;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Version;
/**
* The IndexProviderManager is responsible for maintaining the set of index
* fragments contributed via the CIndex extension point.
* <p>
* For bug 196338, the role of this class was extended. It now has responsibility to look
* at the pool of fragments available depending on their IDs, and select the most appropriate.
* The following rules are applied:
* <ul>
* <li>If a fragment is not compatible, don't use it.</li>
* <li>If multiple fragments are compatible, pick the latest.</li>
* </ul>
*
* A warning is logged if a fragment is contributed which is incompatible, and for which there is
* no compatible equivalent.
*
* It is an internal class, and is public only for testing purposes.
* @since 4.0
*/
public final class IndexProviderManager implements IElementChangedListener {
private static final String ELEMENT_RO_PDOM_PROVIDER= "ReadOnlyPDOMProvider"; //$NON-NLS-1$
private static final String ELEMENT_RO_INDEX_FRAGMENT_PROVIDER= "ReadOnlyIndexFragmentProvider"; //$NON-NLS-1$
private static final String ELEMENT_PROVIDER_USAGE= "FragmentProviderUsage"; //$NON-NLS-1$
@SuppressWarnings("nls")
private static final String
ATTRIBUTE_CLASS = "class",
ATTRIBUTE_NAVIGATION = "navigation",
ATTRIBUTE_CONTENT_ASSIST = "content_assist",
ATTRIBUTE_ADD_IMPORT = "add_import",
ATTRIBUTE_CALL_HIERARCHY = "call_hierarchy",
ATTRIBUTE_TYPE_HIERARCHY = "type_hierarchy",
ATTRIBUTE_INCLUDE_BROWSER = "include_browser",
ATTRIBUTE_SEARCH = "search",
ATTRIBUTE_EDITOR = "editor";
private IIndexFragmentProvider[] fragmentProviders;
private int[] fragmentProviderUsage;
private Map<ProvisionMapKey, Boolean> provisionMap;
private Set<String> compatibleFragmentUnavailable;
private VersionRange pdomVersionRange;
public IndexProviderManager() {
reset();
}
/**
* <b>Note: This method should not be called by clients for purposes other than testing</b>
*/
public void reset() {
Version minVersion= Version.parseVersion(PDOM.versionString(PDOM.getMinSupportedVersion()));
Version maxVersion= Version.parseVersion(PDOM.versionString(PDOM.getMaxSupportedVersion()));
reset(new VersionRange(minVersion, true, maxVersion, true));
}
/**
* <b>Note: This method should not be called by clients for purposes other than testing</b>
* @param pdomVersionRange
*/
public void reset(VersionRange pdomVersionRange) {
this.fragmentProviders= new IIndexFragmentProvider[0];
this.provisionMap= new HashMap<ProvisionMapKey, Boolean>();
this.pdomVersionRange= pdomVersionRange;
this.compatibleFragmentUnavailable= new HashSet<String>();
}
public void startup() {
List<IIndexFragmentProvider> providers = new ArrayList<IIndexFragmentProvider>();
List<IConfigurationElement[]> usageSpecifications= new ArrayList<IConfigurationElement[]>();
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint indexProviders = registry.getExtensionPoint(CCorePlugin.INDEX_UNIQ_ID);
for (IExtension extension : indexProviders.getExtensions()) {
try {
for (IConfigurationElement element : extension.getConfigurationElements()) {
if (ELEMENT_RO_PDOM_PROVIDER.equals(element.getName())) {
Object provider = element.createExecutableExtension(ATTRIBUTE_CLASS);
if (provider instanceof IReadOnlyPDOMProvider) {
providers.add(new ReadOnlyPDOMProviderBridge((IReadOnlyPDOMProvider) provider));
usageSpecifications.add(element.getChildren(ELEMENT_PROVIDER_USAGE));
} else {
CCorePlugin.log(NLS.bind(Messages.IndexProviderManager_InvalidIndexProvider,
extension.getContributor().getName()));
}
} else if (ELEMENT_RO_INDEX_FRAGMENT_PROVIDER.equals(element.getName())) {
Object provider = element.createExecutableExtension(ATTRIBUTE_CLASS);
if (provider instanceof IIndexFragmentProvider) {
providers.add((IIndexFragmentProvider) provider);
usageSpecifications.add(element.getChildren(ELEMENT_PROVIDER_USAGE));
} else {
CCorePlugin.log(NLS.bind(Messages.IndexProviderManager_InvalidIndexProvider,
extension.getContributor().getName()));
}
}
}
} catch (CoreException e) {
CCorePlugin.log(e);
}
}
CoreModel.getDefault().addElementChangedListener(this);
this.fragmentProviders = providers.toArray(new IIndexFragmentProvider[providers.size()]);
this.fragmentProviderUsage= computeProviderUsage(usageSpecifications);
assert fragmentProviders.length == fragmentProviderUsage.length;
}
private int[] computeProviderUsage(List<IConfigurationElement[]> usageFilters) {
int[] usage= new int[usageFilters.size()];
for (int i = 0; i < usage.length; i++) {
IConfigurationElement[] usageFilter= usageFilters.get(i);
usage[i]= computeProviderUsage(usageFilter);
}
return usage;
}
private int computeProviderUsage(IConfigurationElement[] usageFilter) {
if (usageFilter == null || usageFilter.length == 0)
return -1; // Allow usage for all tools.
int result = 0;
IConfigurationElement elem= usageFilter[0];
result |= getOption(elem, ATTRIBUTE_ADD_IMPORT, IIndexManager.ADD_EXTENSION_FRAGMENTS_ADD_IMPORT);
result |= getOption(elem, ATTRIBUTE_CALL_HIERARCHY, IIndexManager.ADD_EXTENSION_FRAGMENTS_CALL_HIERARCHY);
result |= getOption(elem, ATTRIBUTE_CONTENT_ASSIST, IIndexManager.ADD_EXTENSION_FRAGMENTS_CONTENT_ASSIST);
result |= getOption(elem, ATTRIBUTE_INCLUDE_BROWSER, IIndexManager.ADD_EXTENSION_FRAGMENTS_INCLUDE_BROWSER);
result |= getOption(elem, ATTRIBUTE_NAVIGATION, IIndexManager.ADD_EXTENSION_FRAGMENTS_NAVIGATION);
result |= getOption(elem, ATTRIBUTE_SEARCH, IIndexManager.ADD_EXTENSION_FRAGMENTS_SEARCH);
result |= getOption(elem, ATTRIBUTE_TYPE_HIERARCHY, IIndexManager.ADD_EXTENSION_FRAGMENTS_TYPE_HIERARCHY);
result |= getOption(elem, ATTRIBUTE_EDITOR, IIndexManager.ADD_EXTENSION_FRAGMENTS_EDITOR);
return result;
}
public int getOption(IConfigurationElement elem, String attributeName, int option) {
if (Boolean.parseBoolean(elem.getAttribute(attributeName)))
return option;
return 0;
}
/**
* Get the array of IIndexFragment objects provided by all of the
* registered IIndexProvider objects for the specified project, and
* for the current state of the project.
* Order in the array is not significant.
* @param config
* @return the array of IIndexFragment objects for the current state
*/
public IIndexFragment[] getProvidedIndexFragments(ICConfigurationDescription config, int usage) throws CoreException {
Map<String, IIndexFragment> id2fragment = new HashMap<String, IIndexFragment>();
IProject project= config.getProjectDescription().getProject();
for (int i = 0; i < fragmentProviders.length; i++) {
if ((fragmentProviderUsage[i] & usage) != 0) {
IIndexFragmentProvider provider= fragmentProviders[i];
try {
if (providesForProject(provider, project)) {
IIndexFragment[] fragments= provider.getIndexFragments(config);
for (IIndexFragment fragment : fragments) {
try {
processCandidate(id2fragment, fragment);
} catch (InterruptedException e) {
CCorePlugin.log(e); // continue with next candidate
} catch (CoreException e) {
CCorePlugin.log(e); // continue with next candidate
}
}
}
} catch (CoreException e) {
CCorePlugin.log(e); // move to next provider
}
}
}
// Make log entries for any fragments which have no compatible equivalents
List<IIndexFragment> preresult= new ArrayList<IIndexFragment>();
for (Map.Entry<String, IIndexFragment> entry : id2fragment.entrySet()) {
if (entry.getValue() == null) {
String key= entry.getKey();
if (!compatibleFragmentUnavailable.contains(key)) {
String msg= NLS.bind(
Messages.IndexProviderManager_NoCompatibleFragmentsAvailable, key,
collectVersions(config, project, usage, key));
CCorePlugin.log(new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, msg));
compatibleFragmentUnavailable.add(key);
}
} else {
preresult.add(entry.getValue());
}
}
return preresult.toArray(new IIndexFragment[preresult.size()]);
}
/**
* Used for logging a problem.
*/
private String collectVersions(ICConfigurationDescription config, IProject project, int usage, String fragid) {
StringBuilder result= new StringBuilder();
for (int i = 0; i < fragmentProviders.length; i++) {
if ((fragmentProviderUsage[i] & usage) != 0) {
IIndexFragmentProvider provider= fragmentProviders[i];
try {
if (providesForProject(provider, project)) {
IIndexFragment[] fragments= provider.getIndexFragments(config);
for (IIndexFragment fragment : fragments) {
try {
fragment.acquireReadLock();
try {
if (fragid.equals(fragment.getProperty(IIndexFragment.PROPERTY_FRAGMENT_ID))){
String csver = fragment.getProperty(IIndexFragment.PROPERTY_FRAGMENT_FORMAT_VERSION);
if (csver != null) {
if (result.length() > 0)
result.append(", "); //$NON-NLS-1$
result.append(csver);
}
}
} finally {
fragment.releaseReadLock();
}
} catch (Exception e) {
// No logging, we are generating a msg for the log.
}
}
}
} catch (CoreException e) {
// No logging, we are generating a msg for the log.
}
}
}
return result.toString();
}
/**
* Returns the version range supported by the format identified by the specified formatID.
* @param formatID
*/
private VersionRange getCurrentlySupportedVersionRangeForFormat(String formatID) {
if (PDOM.FRAGMENT_PROPERTY_VALUE_FORMAT_ID.equals(formatID)) {
return pdomVersionRange;
}
// Version range checks do not apply to non-PDOM IIndexFragments.
return null;
}
/**
* Examines the candidate fragment, adding it to the map (using its fragment id as key) if
* it compatible with the current run-time, and it is better than any existing fragments for
* the same fragment id.
* @param id2fragment
* @param candidate
*/
private void processCandidate(Map<String, IIndexFragment> id2fragment, IIndexFragment candidate)
throws InterruptedException, CoreException {
String cid= null, csver= null, cformatID= null;
candidate.acquireReadLock();
try {
cid= candidate.getProperty(IIndexFragment.PROPERTY_FRAGMENT_ID);
csver= candidate.getProperty(IIndexFragment.PROPERTY_FRAGMENT_FORMAT_VERSION);
cformatID= candidate.getProperty(IIndexFragment.PROPERTY_FRAGMENT_FORMAT_ID);
} finally {
candidate.releaseReadLock();
}
assert cid != null && csver != null && cformatID != null;
Version cver= Version.parseVersion(csver); // illegal argument exception
IIndexFragment existing= id2fragment.get(cid);
VersionRange versionRange = getCurrentlySupportedVersionRangeForFormat(cformatID);
if (versionRange == null || versionRange.isIncluded(cver)) {
if (existing != null) {
String esver= null, eformatID= null;
existing.acquireReadLock();
try {
esver= existing.getProperty(IIndexFragment.PROPERTY_FRAGMENT_FORMAT_VERSION);
eformatID= existing.getProperty(IIndexFragment.PROPERTY_FRAGMENT_FORMAT_ID);
} finally {
existing.releaseReadLock();
}
if (eformatID.equals(cformatID)) {
Version ever= Version.parseVersion(esver); // illegal argument exception
if (ever.compareTo(cver) < 0) {
id2fragment.put(cid, candidate);
}
} else {
/*
* In future we could allow users to specify
* how format (i.e. PDOM -> custom IIndexFragment implementation)
* changes are coped with.
*/
}
} else {
id2fragment.put(cid, candidate);
}
} else {
if (existing == null) {
id2fragment.put(cid, null); // signifies candidate is unusable
}
}
}
/**
* Adds a PDOM-based index fragment provider.
*
* <b>Note: This method should not be called for purposes other than testing</b>
* @param provider
*/
public void addIndexProvider(IIndexProvider provider) {
if (!(provider instanceof IIndexFragmentProvider)) {
/* This engineering compromise can be resolved when we address whether
* IIndexFragment can be made public. The extension point only accepts
* instances of IOfflinePDOMIndexProvider so this should never happen (tm)
*/
CCorePlugin.log("An unknown index provider implementation was plugged in to the CIndex extension point"); //$NON-NLS-1$
return;
}
final int length = fragmentProviders.length;
IIndexFragmentProvider[] newProviders = new IIndexFragmentProvider[length + 1];
System.arraycopy(fragmentProviders, 0, newProviders, 0, length);
newProviders[length] = (IIndexFragmentProvider) provider;
fragmentProviders = newProviders;
int[] newFilters = new int[length + 1];
System.arraycopy(fragmentProviderUsage, 0, newFilters, 0, length);
newFilters[length] = -1;
fragmentProviderUsage = newFilters;
}
/**
* Removes the specified provider by object identity. Only a PDOM-based provider can be removed
* using this method.
*
* <b>Note: This method should not be called for purposes other than testing</b>
* @param provider
*/
public void removeIndexProvider(IIndexProvider provider) {
for (int i = 0; i < fragmentProviders.length; i++) {
if (fragmentProviders[i] == provider) {
final int length = fragmentProviders.length;
IIndexFragmentProvider[] newProviders = new IIndexFragmentProvider[length - 1];
System.arraycopy(fragmentProviders, 0, newProviders, 0, i);
System.arraycopy(fragmentProviders, i + 1, newProviders, i, length - i - 1);
fragmentProviders = newProviders;
int[] newFilters = new int[length - 1];
System.arraycopy(fragmentProviderUsage, 0, newFilters, 0, i);
System.arraycopy(fragmentProviderUsage, i + 1, newFilters, i, length - i - 1);
fragmentProviderUsage = newFilters;
return;
}
}
}
private boolean providesForProject(IIndexProvider provider, IProject project) {
ProvisionMapKey key= new ProvisionMapKey(provider, project);
if (!provisionMap.containsKey(key)) {
try {
ICProject cproject= CoreModel.getDefault().create(project);
provisionMap.put(key, Boolean.valueOf(provider.providesFor(cproject)));
} catch (CoreException e) {
CCorePlugin.log(e);
provisionMap.put(key, Boolean.FALSE);
}
}
return provisionMap.get(key).booleanValue();
}
@Override
public void elementChanged(ElementChangedEvent event) {
try {
if (event.getType() == ElementChangedEvent.POST_CHANGE) {
processDelta(event.getDelta());
}
} catch (CoreException e) {
CCorePlugin.log(e);
}
}
private void processDelta(ICElementDelta delta) throws CoreException {
int type = delta.getElement().getElementType();
switch (type) {
case ICElement.C_MODEL:
// Loop through the children
ICElementDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; ++i) {
processDelta(children[i]);
}
break;
case ICElement.C_PROJECT:
final ICProject cproject = (ICProject) delta.getElement();
switch (delta.getKind()) {
case ICElementDelta.REMOVED:
List<ProvisionMapKey> toRemove = new ArrayList<>();
for (ProvisionMapKey key : provisionMap.keySet()) {
if (key.getProject().equals(cproject.getProject())) {
toRemove.add(key);
}
}
for (ProvisionMapKey key : toRemove) {
provisionMap.remove(key);
}
break;
}
}
}
private static class ProvisionMapKey {
private final IIndexProvider provider;
private final IProject project;
ProvisionMapKey(IIndexProvider provider, IProject project) {
this.provider= provider;
this.project= project;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ProvisionMapKey) {
ProvisionMapKey other= (ProvisionMapKey) obj;
return other.project.equals(project) && other.provider.equals(provider);
}
return false;
}
@Override
public int hashCode() {
return project.hashCode() ^ provider.hashCode();
}
public IProject getProject() {
return project;
}
}
}