/*******************************************************************************
* Copyright (c) 2010, 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.internal.core.language.settings.providers;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.cdtvariables.CdtVariableException;
import org.eclipse.cdt.core.cdtvariables.ICdtVariableManager;
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.LanguageSettingsManager;
import org.eclipse.cdt.core.parser.ExtendedScannerInfo;
import org.eclipse.cdt.core.parser.IScannerInfo;
import org.eclipse.cdt.core.parser.IScannerInfoChangeListener;
import org.eclipse.cdt.core.parser.IScannerInfoProvider;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
import org.eclipse.cdt.core.settings.model.ICMacroEntry;
import org.eclipse.cdt.core.settings.model.ICPathEntry;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;
import org.eclipse.cdt.core.settings.model.util.CDataUtil;
import org.eclipse.cdt.internal.core.parser.ParserSettings2;
import org.eclipse.cdt.internal.core.settings.model.CProjectDescriptionManager;
import org.eclipse.cdt.internal.core.settings.model.SettingsModelMessages;
import org.eclipse.cdt.utils.EFSExtensionManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
/**
* Implementation of {@link IScannerInfoProvider} backed by the list of
* language settings providers of "default settings configuration"
* (see {@link ICProjectDescription#getDefaultSettingConfiguration()}).
*
* @see IScannerInfo#getIncludePaths()
*
*/
public class LanguageSettingsScannerInfoProvider implements IScannerInfoProvider, ILanguageSettingsChangeListener {
private static final String FRAMEWORK_PRIVATE_HEADERS_INCLUDE = "/__framework__.framework/PrivateHeaders/__header__"; //$NON-NLS-1$
private static final String FRAMEWORK_HEADERS_INCLUDE = "/__framework__.framework/Headers/__header__"; //$NON-NLS-1$
private static final ExtendedScannerInfo DUMMY_SCANNER_INFO = new ExtendedScannerInfo();
private Map<IResource, List<IScannerInfoChangeListener>> listenersMap = null;
@Override
public ExtendedScannerInfo getScannerInformation(IResource rc) {
IProject project = rc.getProject();
if (project==null)
return DUMMY_SCANNER_INFO;
ICProjectDescription prjDescription = CProjectDescriptionManager.getInstance().getProjectDescription(project, false);
if (prjDescription==null)
return DUMMY_SCANNER_INFO;
ICConfigurationDescription cfgDescription = prjDescription.getDefaultSettingConfiguration();
if (cfgDescription==null)
return DUMMY_SCANNER_INFO;
List<String> languageIds = LanguageSettingsManager.getLanguages(rc, cfgDescription);
if (languageIds.isEmpty()) {
String msg = NLS.bind(SettingsModelMessages.getString("LanguageSettingsScannerInfoProvider.UnableToDetermineLanguage"), rc.toString()); //$NON-NLS-1$
IStatus status = new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, msg, new Exception());
CCorePlugin.log(status);
return DUMMY_SCANNER_INFO;
}
LinkedHashSet<ICLanguageSettingEntry> includePathEntries = new LinkedHashSet<ICLanguageSettingEntry>();
LinkedHashSet<ICLanguageSettingEntry> includePathLocalEntries = new LinkedHashSet<ICLanguageSettingEntry>();
LinkedHashSet<ICLanguageSettingEntry> includeFileEntries = new LinkedHashSet<ICLanguageSettingEntry>();
LinkedHashSet<ICLanguageSettingEntry> macroFileEntries = new LinkedHashSet<ICLanguageSettingEntry>();
LinkedHashSet<ICLanguageSettingEntry> macroEntries = new LinkedHashSet<ICLanguageSettingEntry>();
for (String langId : languageIds) {
List<ICLanguageSettingEntry> incSys = LanguageSettingsProvidersSerializer.getSystemSettingEntriesByKind(cfgDescription, rc, langId,
ICSettingEntry.INCLUDE_PATH);
includePathEntries.addAll(incSys);
List<ICLanguageSettingEntry> incLocal = LanguageSettingsProvidersSerializer.getLocalSettingEntriesByKind(cfgDescription, rc, langId,
ICSettingEntry.INCLUDE_PATH);
includePathLocalEntries.addAll(incLocal);
List<ICLanguageSettingEntry> incFiles = LanguageSettingsProvidersSerializer.getSettingEntriesByKind(cfgDescription, rc, langId,
ICSettingEntry.INCLUDE_FILE);
includeFileEntries.addAll(incFiles);
List<ICLanguageSettingEntry> macroFiles = LanguageSettingsProvidersSerializer.getSettingEntriesByKind(cfgDescription, rc, langId,
ICSettingEntry.MACRO_FILE);
macroFileEntries.addAll(macroFiles);
List<ICLanguageSettingEntry> macros = LanguageSettingsProvidersSerializer.getSettingEntriesByKind(cfgDescription, rc, langId,
ICSettingEntry.MACRO);
macroEntries.addAll(macros);
}
String[] includePaths = convertToLocations(includePathEntries, cfgDescription);
String[] includePathsLocal = convertToLocations(includePathLocalEntries, cfgDescription);
String[] includeFiles = convertToLocations(includeFileEntries, cfgDescription);
String[] macroFiles = convertToLocations(macroFileEntries, cfgDescription);
Map<String, String> definedMacros = new HashMap<String, String>();
for (ICLanguageSettingEntry entry : macroEntries) {
ICMacroEntry macroEntry = (ICMacroEntry)entry;
String name = macroEntry.getName();
String value = macroEntry.getValue();
definedMacros.put(name, value);
}
ExtendedScannerInfo extendedScannerInfo = new ExtendedScannerInfo(definedMacros, includePaths, macroFiles, includeFiles, includePathsLocal);
extendedScannerInfo.setParserSettings(new ParserSettings2(project));
return extendedScannerInfo;
}
private String expandVariables(String pathStr, ICConfigurationDescription cfgDescription) {
try {
ICdtVariableManager varManager = CCorePlugin.getDefault().getCdtVariableManager();
pathStr = varManager.resolveValue(pathStr, "", null, cfgDescription); //$NON-NLS-1$
} catch (Exception e) {
// Swallow exceptions but also log them
CCorePlugin.log(e);
}
return pathStr;
}
/**
* Get build working directory for the provided configuration. Returns
* project location if none defined.
*/
private static IPath getBuildCWD(ICConfigurationDescription cfgDescription) {
IPath buildCWD = cfgDescription.getBuildSetting().getBuilderCWD();
if (buildCWD == null) {
IProject project = cfgDescription.getProjectDescription().getProject();
buildCWD = project.getLocation();
} else {
ICdtVariableManager mngr = CCorePlugin.getDefault().getCdtVariableManager();
try {
// Note that IPath buildCWD holding variables is mis-constructed,
// i.e. ${workspace_loc:/path} gets split into 2 path segments
// still, MBS does that and we need to handle that
String buildPathString = buildCWD.toString();
buildPathString = mngr.resolveValue(buildPathString, "", null, cfgDescription); //$NON-NLS-1$
if (!buildPathString.isEmpty()) {
buildCWD = new Path(buildPathString);
} else {
IProject project = cfgDescription.getProjectDescription().getProject();
URI locationURI = project.getLocationURI();
String path = EFSExtensionManager.getDefault().getPathFromURI(locationURI);
buildCWD = new Path(path);
}
} catch (CdtVariableException e) {
CCorePlugin.log(e);
}
}
buildCWD = buildCWD.addTrailingSeparator();
return buildCWD;
}
/**
* Resolve location to file system location in a configuration context.
* Resolving includes replacing build/environment variables with values, making relative path absolute etc.
*
* @param location - location to resolve. If relative, it is taken to be rooted in build working directory.
* @param cfgDescription - the configuration context.
* @return resolved file system location.
*/
private static String resolveEntry(String location, ICConfigurationDescription cfgDescription) {
// Substitute build/environment variables
ICdtVariableManager varManager = CCorePlugin.getDefault().getCdtVariableManager();
try {
location = varManager.resolveValue(location, "", null, cfgDescription); //$NON-NLS-1$
} catch (CdtVariableException e) {
// Swallow exceptions but also log them
CCorePlugin.log(e);
}
// use OS file separators (i.e. '\' on Windows)
if (java.io.File.separatorChar != IPath.SEPARATOR) {
location = location.replace(IPath.SEPARATOR, java.io.File.separatorChar);
}
IPath locPath = new Path(location);
if (!locPath.isUNC() && locPath.isAbsolute() && locPath.getDevice() == null) {
IPath buildCWD = getBuildCWD(cfgDescription);
// prepend device (C:) for Windows
String device = buildCWD.getDevice();
if (device != null) {
// note that we avoid using org.eclipse.core.runtime.Path for manipulations being careful
// to preserve "../" segments and not let collapsing them which is not correct for symbolic links.
location = device + location;
}
}
if (!locPath.isAbsolute()) {
// consider relative path to be from build working directory
IPath buildCWD = getBuildCWD(cfgDescription);
// again, we avoid using org.eclipse.core.runtime.Path for manipulations being careful
// to preserve "../" segments and not let collapsing them which is not correct for symbolic links.
location = buildCWD.addTrailingSeparator().toOSString() + location;
}
return location;
}
/**
* Convert path delimiters to OS representation avoiding using org.eclipse.core.runtime.Path
* being careful to preserve "../" segments and not let collapsing them which is not correct for symbolic links.
*/
private String toOSString(String loc) {
// use OS file separators (i.e. '\' on Windows)
if (java.io.File.separatorChar != IPath.SEPARATOR) {
loc = loc.replace(IPath.SEPARATOR, java.io.File.separatorChar);
}
return loc;
}
/**
* Convert the path entries to absolute file system locations represented as String array.
* Resolve the entries which are not resolved.
*
* @param entriesPath - language settings path entries.
* @param cfgDescription - configuration description for resolving entries.
* @return array of the locations.
*/
private String[] convertToLocations(LinkedHashSet<ICLanguageSettingEntry> entriesPath, ICConfigurationDescription cfgDescription) {
List<String> locations = new ArrayList<String>(entriesPath.size());
for (ICLanguageSettingEntry entry : entriesPath) {
ICPathEntry entryPath = (ICPathEntry)entry;
if (entryPath.isValueWorkspacePath()) {
ICLanguageSettingEntry[] entries = new ICLanguageSettingEntry[] {entry};
if (!entry.isResolved()) {
entries = CDataUtil.resolveEntries(entries, cfgDescription);
}
for (ICLanguageSettingEntry resolved : entries) {
IPath loc = ((ICPathEntry) resolved).getLocation();
if (loc != null) {
if (checkBit(resolved.getFlags(), ICSettingEntry.FRAMEWORKS_MAC)) {
// handle frameworks, see IScannerInfo.getIncludePaths()
locations.add(loc.append(FRAMEWORK_HEADERS_INCLUDE).toOSString());
locations.add(loc.append(FRAMEWORK_PRIVATE_HEADERS_INCLUDE).toOSString());
} else {
locations.add(loc.toOSString());
}
}
}
} else {
// have to use getName() rather than getLocation() to avoid collapsing ".."
String loc = entryPath.getName();
if (entryPath.isResolved()) {
locations.add(loc);
} else {
loc = resolveEntry(loc, cfgDescription);
if (loc != null) {
if (checkBit(entryPath.getFlags(), ICSettingEntry.FRAMEWORKS_MAC)) {
// handle frameworks, see IScannerInfo.getIncludePaths()
locations.add(toOSString(loc + FRAMEWORK_HEADERS_INCLUDE));
locations.add(toOSString(loc + FRAMEWORK_PRIVATE_HEADERS_INCLUDE));
} else {
locations.add(toOSString(loc));
String unresolvedPath = entryPath.getName();
if (!new Path(unresolvedPath).isAbsolute()) {
// add relative paths again for indexer to resolve from source file location
String expandedPath = expandVariables(unresolvedPath, cfgDescription);
if (!expandedPath.isEmpty() && !new Path(expandedPath).isAbsolute()) {
locations.add(toOSString(expandedPath));
}
}
}
}
}
}
}
return locations.toArray(new String[locations.size()]);
}
private static boolean checkBit(int flags, int bit) {
return (flags & bit) == bit;
}
@Override
public void subscribe(IResource resource, IScannerInfoChangeListener listener) {
if (resource == null || listener == null) {
return;
}
if (listenersMap == null) {
listenersMap = Collections.synchronizedMap(new HashMap<IResource, List<IScannerInfoChangeListener>>());
}
IProject project = resource.getProject();
List<IScannerInfoChangeListener> list = listenersMap.get(project);
if (list == null) {
list = new Vector<IScannerInfoChangeListener>();
listenersMap.put(project, list);
}
if (!list.contains(listener)) {
list.add(listener);
}
}
@Override
public void unsubscribe(IResource resource, IScannerInfoChangeListener listener) {
if (resource == null || listener == null) {
return;
}
IProject project = resource.getProject();
if (listenersMap != null) {
List<IScannerInfoChangeListener> list = listenersMap.get(project);
if (list != null) {
list.remove(listener);
}
}
}
@Override
public void handleEvent(ILanguageSettingsChangeEvent event) {
if (listenersMap == null || listenersMap.isEmpty()) {
return;
}
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(event.getProjectName());
if (project != null) {
ICProjectDescription prjDescription = CCorePlugin.getDefault().getProjectDescription(project);
if (prjDescription != null) {
ICConfigurationDescription indexedCfgDescription = prjDescription.getDefaultSettingConfiguration();
String indexedCfgId = indexedCfgDescription.getId();
for (String cfgId : event.getConfigurationDescriptionIds()) {
if (cfgId.equals(indexedCfgId)) {
for (Entry<IResource, List<IScannerInfoChangeListener>> entry : listenersMap.entrySet()) {
IResource rc = entry.getKey();
List<IScannerInfoChangeListener> listeners = listenersMap.get(rc);
if (listeners != null && !listeners.isEmpty()) {
IScannerInfo info = getScannerInformation(rc);
for (IScannerInfoChangeListener listener : listeners) {
listener.changeNotification(rc, info);
}
}
}
break;
}
}
}
}
}
}