| /******************************************************************************* |
| * Copyright (c) 2009, 2014 Andrew Gvozdev and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Andrew Gvozdev - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.cdt.managedbuilder.language.settings.providers; |
| |
| import java.io.File; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.cdt.core.CCorePlugin; |
| import org.eclipse.cdt.core.EFSExtensionProvider; |
| import org.eclipse.cdt.core.ErrorParserManager; |
| import org.eclipse.cdt.core.cdtvariables.CdtVariableException; |
| import org.eclipse.cdt.core.cdtvariables.ICdtVariableManager; |
| import org.eclipse.cdt.core.language.settings.providers.ICBuildOutputParser; |
| import org.eclipse.cdt.core.language.settings.providers.IWorkingDirectoryTracker; |
| import org.eclipse.cdt.core.language.settings.providers.LanguageSettingsManager; |
| import org.eclipse.cdt.core.language.settings.providers.LanguageSettingsSerializableProvider; |
| import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; |
| import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry; |
| import org.eclipse.cdt.core.settings.model.ICSettingEntry; |
| import org.eclipse.cdt.core.settings.model.util.CDataUtil; |
| import org.eclipse.cdt.internal.core.LRUCache; |
| import org.eclipse.cdt.internal.core.XmlUtil; |
| import org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin; |
| import org.eclipse.cdt.utils.EFSExtensionManager; |
| import org.eclipse.cdt.utils.cdtvariables.CdtVariableResolver; |
| import org.eclipse.core.filesystem.EFS; |
| 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.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.content.IContentType; |
| import org.eclipse.core.runtime.content.IContentTypeManager; |
| import org.w3c.dom.Element; |
| |
| /** |
| * Abstract class for language settings providers capable to parse build output. |
| * <p> |
| * <strong>EXPERIMENTAL</strong>. This class interface is not stable yet as |
| * it is not currently (CDT 8.1, Juno) clear how it may need to be used in future. |
| * There is no guarantee that this API will work or that it will remain the same. |
| * Please do not use this API without consulting with the CDT team. |
| * </p> |
| * @noextend This class is not intended to be subclassed by clients. |
| * |
| * @since 8.1 |
| */ |
| public abstract class AbstractLanguageSettingsOutputScanner extends LanguageSettingsSerializableProvider |
| implements ICBuildOutputParser { |
| protected static final String ATTR_KEEP_RELATIVE_PATHS = "keep-relative-paths"; //$NON-NLS-1$ |
| // evaluates to "/${ProjName)/" |
| private static final String PROJ_NAME_PREFIX = '/' |
| + CdtVariableResolver.createVariableReference(CdtVariableResolver.VAR_PROJ_NAME) + '/'; |
| |
| protected ICConfigurationDescription currentCfgDescription = null; |
| protected IWorkingDirectoryTracker cwdTracker = null; |
| protected IProject currentProject = null; |
| protected IResource currentResource = null; |
| protected String currentLanguageId = null; |
| |
| protected String parsedResourceName = null; |
| protected boolean isResolvingPaths = true; |
| |
| private static final int FIND_RESOURCES_CACHE_SIZE = 100; |
| |
| private LRUCache<URI, IResource[]> workspaceRootFindContainersForLocationURICache = new LRUCache<>( |
| FIND_RESOURCES_CACHE_SIZE); |
| private LRUCache<URI, IResource[]> workspaceRootFindFilesForLocationURICache = new LRUCache<>( |
| FIND_RESOURCES_CACHE_SIZE); |
| private HashMap<IProject, LRUCache<IPath, List<IResource>>> findPathInProjectCache = new HashMap<>(); |
| |
| /** @since 8.2 */ |
| protected EFSExtensionProvider efsProvider = null; |
| |
| private static final EFSExtensionProvider efsProviderDefault = new EFSExtensionProvider() { |
| final EFSExtensionManager efsManager = EFSExtensionManager.getDefault(); |
| |
| @Override |
| public String getPathFromURI(URI locationURI) { |
| return efsManager.getPathFromURI(locationURI); |
| } |
| |
| @Override |
| public URI getLinkedURI(URI locationURI) { |
| return efsManager.getLinkedURI(locationURI); |
| } |
| |
| @Override |
| public URI createNewURIFromPath(URI locationOnSameFilesystem, String path) { |
| return efsManager.createNewURIFromPath(locationOnSameFilesystem, path); |
| } |
| |
| @Override |
| public String getMappedPath(URI locationURI) { |
| return efsManager.getMappedPath(locationURI); |
| } |
| |
| @Override |
| public boolean isVirtual(URI locationURI) { |
| return efsManager.isVirtual(locationURI); |
| } |
| |
| @Override |
| public URI append(URI baseURI, String extension) { |
| return efsManager.append(baseURI, extension); |
| } |
| }; |
| |
| /** |
| * Abstract class defining common functionality for option parsers. |
| * The purpose of this parser is to parse a portion of string representing |
| * a single option and create a language settings entry out of it. |
| * |
| * See {@link GCCBuildCommandParser} for an example how to define the parsers. |
| */ |
| protected static abstract class AbstractOptionParser { |
| private final int kind; |
| private final Pattern pattern; |
| private final String nameExpression; |
| private final String valueExpression; |
| private final int extraFlag; |
| |
| private String parsedName; |
| private String parsedValue; |
| private final Pattern removeExtraFileNamePattern; |
| |
| /** |
| * Constructor. |
| * |
| * @param kind - kind of language settings entries being parsed by the parser. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * The pattern may be embedded into another pattern for intermediate |
| * parsing so it is best to avoid using numbered group back-reference e.g. \1 |
| * @param nameExpression - capturing group expression (numbered or named) defining name of an entry. |
| * @param valueExpression - capturing group expression (numbered or named) defining value of an entry. |
| * @param extraFlag - extra-flag to add while creating language settings entry. |
| */ |
| public AbstractOptionParser(int kind, String pattern, String nameExpression, String valueExpression, |
| int extraFlag) { |
| this.kind = kind; |
| this.nameExpression = nameExpression; |
| this.valueExpression = valueExpression; |
| this.extraFlag = extraFlag; |
| |
| this.pattern = Pattern.compile(pattern); |
| this.removeExtraFileNamePattern = Pattern.compile("(" + pattern + ").*"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| /** |
| * Create language settings entry of appropriate kind and considering extra-flag passed in constructor. |
| * |
| * @param name - name of language settings entry. |
| * @param value - value of language settings entry. |
| * @param flag - flag to set. Note that the flag will be amended with the extra-flag defined in constructor. |
| * @return new language settings entry. |
| */ |
| public ICLanguageSettingEntry createEntry(String name, String value, int flag) { |
| return (ICLanguageSettingEntry) CDataUtil.createEntry(kind, name, value, null, flag | extraFlag); |
| } |
| |
| /** |
| * Check if the king of option parsed by parser is "file". |
| * |
| * @return {@code true} if the kind is file, {@code false} otherwise. |
| */ |
| public boolean isForFile() { |
| return kind == ICSettingEntry.INCLUDE_FILE || kind == ICSettingEntry.MACRO_FILE; |
| } |
| |
| /** |
| * Check if the king of option parsed by parser is "folder". |
| * |
| * @return {@code true} if the kind is folder, {@code false} otherwise. |
| */ |
| public boolean isForFolder() { |
| return kind == ICSettingEntry.INCLUDE_PATH || kind == ICSettingEntry.LIBRARY_PATH; |
| } |
| |
| /** |
| * Return value represented by the capturing group expression. |
| */ |
| private String parseStr(Matcher matcher, String str) { |
| if (str != null) |
| return matcher.replaceAll(str); |
| return null; |
| } |
| |
| /** |
| * Test for a match and parse a portion of input string representing a single option |
| * to retrieve name and value. |
| * |
| * @param optionString - an option to test and parse, possibly with an argument. |
| * @return {@code true} if the option is a match to parser's regular expression |
| * or {@code false} otherwise. |
| */ |
| public boolean parseOption(String optionString) { |
| // get rid of extra text at the end (for example file name could be confused for an argument) |
| Matcher matcherRemoveExtra = removeExtraFileNamePattern.matcher(optionString); |
| String option = optionString; |
| if (!matcherRemoveExtra.matches()) { |
| return false; |
| } |
| option = matcherRemoveExtra.group(1); |
| |
| Matcher matcher = pattern.matcher(option); |
| boolean isMatch = matcher.matches(); |
| if (isMatch) { |
| parsedName = parseStr(matcher, nameExpression); |
| parsedValue = parseStr(matcher, valueExpression); |
| } |
| return isMatch; |
| } |
| } |
| |
| /** |
| * Implementation of {@link AbstractOptionParser} for include path options parsing. |
| */ |
| protected static class IncludePathOptionParser extends AbstractOptionParser { |
| public IncludePathOptionParser(String pattern, String nameExpression) { |
| super(ICLanguageSettingEntry.INCLUDE_PATH, pattern, nameExpression, nameExpression, 0); |
| } |
| |
| public IncludePathOptionParser(String pattern, String nameExpression, int extraFlag) { |
| super(ICLanguageSettingEntry.INCLUDE_PATH, pattern, nameExpression, nameExpression, extraFlag); |
| } |
| } |
| |
| /** |
| * Implementation of {@link AbstractOptionParser} for include file options parsing. |
| */ |
| protected static class IncludeFileOptionParser extends AbstractOptionParser { |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| */ |
| public IncludeFileOptionParser(String pattern, String nameExpression) { |
| super(ICLanguageSettingEntry.INCLUDE_FILE, pattern, nameExpression, nameExpression, 0); |
| } |
| |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| * @param extraFlag - extra-flag to add while creating language settings entry. |
| */ |
| public IncludeFileOptionParser(String pattern, String nameExpression, int extraFlag) { |
| super(ICLanguageSettingEntry.INCLUDE_FILE, pattern, nameExpression, nameExpression, extraFlag); |
| } |
| } |
| |
| /** |
| * Implementation of {@link AbstractOptionParser} for macro options parsing. |
| */ |
| protected static class MacroOptionParser extends AbstractOptionParser { |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| * @param valueExpression - capturing group expression defining value of an entry. |
| */ |
| public MacroOptionParser(String pattern, String nameExpression, String valueExpression) { |
| super(ICLanguageSettingEntry.MACRO, pattern, nameExpression, valueExpression, 0); |
| } |
| |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| * @param valueExpression - capturing group expression defining value of an entry. |
| * @param extraFlag - extra-flag to add while creating language settings entry. |
| */ |
| public MacroOptionParser(String pattern, String nameExpression, String valueExpression, int extraFlag) { |
| super(ICLanguageSettingEntry.MACRO, pattern, nameExpression, valueExpression, extraFlag); |
| } |
| |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| * @param extraFlag - extra-flag to add while creating language settings entry. |
| */ |
| public MacroOptionParser(String pattern, String nameExpression, int extraFlag) { |
| super(ICLanguageSettingEntry.MACRO, pattern, nameExpression, null, extraFlag); |
| } |
| } |
| |
| /** |
| * Implementation of {@link AbstractOptionParser} for macro file options parsing. |
| */ |
| protected static class MacroFileOptionParser extends AbstractOptionParser { |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| */ |
| public MacroFileOptionParser(String pattern, String nameExpression) { |
| super(ICLanguageSettingEntry.MACRO_FILE, pattern, nameExpression, nameExpression, 0); |
| } |
| |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| * @param extraFlag - extra-flag to add while creating language settings entry. |
| */ |
| public MacroFileOptionParser(String pattern, String nameExpression, int extraFlag) { |
| super(ICLanguageSettingEntry.MACRO_FILE, pattern, nameExpression, nameExpression, extraFlag); |
| } |
| } |
| |
| /** |
| * Implementation of {@link AbstractOptionParser} for library path options parsing. |
| */ |
| protected static class LibraryPathOptionParser extends AbstractOptionParser { |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| */ |
| public LibraryPathOptionParser(String pattern, String nameExpression) { |
| super(ICLanguageSettingEntry.LIBRARY_PATH, pattern, nameExpression, nameExpression, 0); |
| } |
| |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| * @param extraFlag - extra-flag to add while creating language settings entry. |
| */ |
| public LibraryPathOptionParser(String pattern, String nameExpression, int extraFlag) { |
| super(ICLanguageSettingEntry.LIBRARY_PATH, pattern, nameExpression, nameExpression, extraFlag); |
| } |
| } |
| |
| /** |
| * Implementation of {@link AbstractOptionParser} for library file options parsing. |
| */ |
| protected static class LibraryFileOptionParser extends AbstractOptionParser { |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| */ |
| public LibraryFileOptionParser(String pattern, String nameExpression) { |
| super(ICLanguageSettingEntry.LIBRARY_FILE, pattern, nameExpression, nameExpression, 0); |
| } |
| |
| /** |
| * Constructor. |
| * @param pattern - regular expression pattern being parsed by the parser. |
| * @param nameExpression - capturing group expression defining name of an entry. |
| * @param extraFlag - extra-flag to add while creating language settings entry. |
| */ |
| public LibraryFileOptionParser(String pattern, String nameExpression, int extraFlag) { |
| super(ICLanguageSettingEntry.LIBRARY_FILE, pattern, nameExpression, nameExpression, extraFlag); |
| } |
| } |
| |
| /** |
| * Parse the line returning the resource name as appears in the output. |
| * This is the resource where {@link ICLanguageSettingEntry} list is being added. |
| * |
| * @param line - one input line from the output stripped from end of line characters. |
| * @return the resource name as appears in the output or {@code null}. |
| * Note that {@code null} can have different semantics and can mean "no resource found" |
| * or "applicable to any resource". By default "no resource found" is used in this |
| * abstract class but extenders can handle otherwise. |
| */ |
| protected abstract String parseResourceName(String line); |
| |
| /** |
| * Parse the line returning the list of substrings to be treated each as input to |
| * the option parsers. It is assumed that each substring presents one |
| * {@link ICLanguageSettingEntry} (for example compiler options {@code -I/path} or |
| * {@code -DMACRO=1}). |
| * |
| * @param line - one input line from the output stripped from end of line characters. |
| * @return list of substrings representing language settings entries. |
| */ |
| protected abstract List<String> parseOptions(String line); |
| |
| /** |
| * @return array of option parsers defining how to parse a string to |
| * {@link ICLanguageSettingEntry}. |
| * See {@link AbstractOptionParser} and its specific extenders. |
| */ |
| protected abstract AbstractOptionParser[] getOptionParsers(); |
| |
| /** |
| * @return array of option parsers defining how to parse a string to |
| * {@link ICLanguageSettingEntry}. |
| * See {@link AbstractOptionParser} and its specific extenders. |
| * |
| * @param optionToParse the option string to be parsed. |
| * This can be used as a hint in order to return a subset of parsers, for better performance. |
| * |
| * @since 9.1 |
| */ |
| protected AbstractOptionParser[] getOptionParsers(String optionToParse) { |
| return getOptionParsers(); |
| } |
| |
| /** |
| * @return {@code true} when the provider tries to resolve relative or remote paths |
| * to the existing paths in the workspace or local file-system using certain heuristics. |
| */ |
| public boolean isResolvingPaths() { |
| return isResolvingPaths; |
| } |
| |
| /** |
| * Enable or disable resolving relative or remote paths to the existing paths |
| * in the workspace or local file-system. |
| * |
| * @param resolvePaths - set {@code true} to enable or {@code false} to disable |
| * resolving paths. When this parameter is set to {@code false} the paths will |
| * be kept as they appear in the build output. |
| */ |
| public void setResolvingPaths(boolean resolvePaths) { |
| this.isResolvingPaths = resolvePaths; |
| } |
| |
| @Override |
| public void startup(ICConfigurationDescription cfgDescription, IWorkingDirectoryTracker cwdTracker) |
| throws CoreException { |
| this.currentCfgDescription = cfgDescription; |
| this.currentProject = cfgDescription != null ? cfgDescription.getProjectDescription().getProject() : null; |
| this.cwdTracker = cwdTracker; |
| this.efsProvider = getEFSProvider(); |
| } |
| |
| @Override |
| public void shutdown() { |
| // release resources for garbage collector |
| // but keep currentCfgDescription for AbstractBuiltinSpecsDetector flow |
| parsedResourceName = null; |
| currentLanguageId = null; |
| currentResource = null; |
| cwdTracker = null; |
| clearCaches(); |
| } |
| |
| private void clearCaches() { |
| workspaceRootFindContainersForLocationURICache.clear(); |
| workspaceRootFindFilesForLocationURICache.clear(); |
| findPathInProjectCache.clear(); |
| } |
| |
| @Override |
| public boolean processLine(String line) { |
| parsedResourceName = parseResourceName(line); |
| currentResource = findResource(parsedResourceName); |
| |
| currentLanguageId = determineLanguage(); |
| if (!isLanguageInScope(currentLanguageId)) { |
| return false; |
| } |
| |
| /** |
| * URI of directory where the build is happening. This URI could point to a remote file-system |
| * for remote builds. Most often it is the same file-system as for currentResource but |
| * it can be different file-system (and different URI schema). |
| */ |
| URI buildDirURI = null; |
| |
| /** |
| * Where source tree starts if mapped. This kind of mapping is useful for example in cases when |
| * the absolute path to the source file on the remote system is simulated inside a project in the |
| * workspace. |
| * This URI is rooted on the same file-system where currentResource resides. In general this file-system |
| * (or even URI schema) does not have to match that of buildDirURI. |
| */ |
| URI mappedRootURI = null; |
| |
| if (isResolvingPaths) { |
| mappedRootURI = getMappedRootURI(currentResource, parsedResourceName); |
| buildDirURI = getBuildDirURI(mappedRootURI); |
| } |
| |
| List<ICLanguageSettingEntry> entries = new ArrayList<>(); |
| |
| List<String> options = parseOptions(line); |
| if (options != null) { |
| for (String option : options) { |
| AbstractOptionParser[] optionParsers = getOptionParsers(option); |
| for (AbstractOptionParser optionParser : optionParsers) { |
| try { |
| if (optionParser.parseOption(option)) { |
| ICLanguageSettingEntry entry = null; |
| if (isResolvingPaths && (optionParser.isForFile() || optionParser.isForFolder())) { |
| URI baseURI = mappedRootURI; |
| if (buildDirURI != null && !new Path(optionParser.parsedName).isAbsolute()) { |
| if (mappedRootURI != null) { |
| baseURI = efsProvider.append(mappedRootURI, buildDirURI.getPath()); |
| } else { |
| baseURI = buildDirURI; |
| } |
| } |
| entry = createResolvedPathEntry(optionParser, optionParser.parsedName, 0, baseURI); |
| } else { |
| entry = optionParser.createEntry(optionParser.parsedName, optionParser.parsedValue, 0); |
| } |
| |
| if (entry != null && !entries.contains(entry)) { |
| entries.add(entry); |
| break; |
| } |
| } |
| } catch (Throwable e) { |
| @SuppressWarnings("nls") |
| String msg = "Exception trying to parse option [" + option + "], class " |
| + getClass().getSimpleName(); |
| ManagedBuilderCorePlugin |
| .log(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.PLUGIN_ID, msg, e)); |
| } |
| } |
| } |
| if (entries.size() > 0) { |
| setSettingEntries(entries); |
| } else { |
| setSettingEntries(null); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * In case when absolute path is mapped to the source tree in a project |
| * this function will try to figure mapping and return "mapped root", |
| * i.e URI where the root path would be mapped. The mapped root will be |
| * used to prepend to other "absolute" paths where appropriate. |
| * |
| * @param resource - a resource referred by parsed path |
| * @param parsedResourceName - path as appears in the output |
| * @return mapped path as URI |
| */ |
| protected URI getMappedRootURI(IResource resource, String parsedResourceName) { |
| if (resource == null) { |
| return null; |
| } |
| |
| URI resourceURI = resource.getLocationURI(); |
| String mappedRoot = "/"; //$NON-NLS-1$ |
| |
| if (parsedResourceName != null) { |
| IPath parsedSrcPath = new Path(parsedResourceName); |
| if (parsedSrcPath.isAbsolute()) { |
| IPath absResourcePath = resource.getLocation(); |
| int absSegmentsCount = absResourcePath.segmentCount(); |
| int relSegmentsCount = parsedSrcPath.segmentCount(); |
| if (absSegmentsCount >= relSegmentsCount) { |
| IPath ending = absResourcePath.removeFirstSegments(absSegmentsCount - relSegmentsCount); |
| ending = ending.setDevice(parsedSrcPath.getDevice()).makeAbsolute(); |
| if (ending.equals(parsedSrcPath.makeAbsolute())) { |
| // mappedRoot here is parsedSrcPath with removed parsedResourceName trailing segments, |
| // i.e. if absResourcePath="/path/workspace/project/file.c" and parsedResourceName="project/file.c" |
| // then mappedRoot="/path/workspace/" |
| mappedRoot = absResourcePath.removeLastSegments(relSegmentsCount).toString(); |
| } |
| } |
| } |
| } |
| // this creates URI with schema and other components from resourceURI but path as mappedRoot |
| URI uri = efsProvider.createNewURIFromPath(resourceURI, mappedRoot); |
| return uri; |
| } |
| |
| /** |
| * Determine current build directory considering currentResource (resource being compiled), |
| * parsedResourceName and mappedRootURI. |
| * |
| * @param mappedRootURI - root of the source tree when mapped to remote file-system. |
| * @return {@link URI} of current build directory |
| */ |
| protected URI getBuildDirURI(URI mappedRootURI) { |
| URI buildDirURI = null; |
| |
| // try to deduce build directory from full path of currentResource and partial path of parsedResourceName |
| URI cwdURI = null; |
| if (currentResource != null && parsedResourceName != null && !new Path(parsedResourceName).isAbsolute()) { |
| cwdURI = findBaseLocationURI(currentResource.getLocationURI(), parsedResourceName); |
| } |
| String cwdPath = cwdURI != null ? efsProvider.getPathFromURI(cwdURI) : null; |
| if (cwdPath != null && mappedRootURI != null) { |
| buildDirURI = efsProvider.append(mappedRootURI, cwdPath); |
| } else { |
| buildDirURI = cwdURI; |
| } |
| |
| // try IWorkingDirectoryTracker |
| if (buildDirURI == null && cwdTracker != null) { |
| buildDirURI = cwdTracker.getWorkingDirectoryURI(); |
| } |
| |
| // try builder working directory |
| if (buildDirURI == null && currentCfgDescription != null) { |
| IPath pathBuilderCWD = currentCfgDescription.getBuildSetting().getBuilderCWD(); |
| if (pathBuilderCWD != null) { |
| String builderCWD = pathBuilderCWD.toString(); |
| try { |
| // here is a hack to overcome ${workspace_loc:/prj-name} returned by builder |
| // where "/" is treated as path separator by pathBuilderCWD |
| ICdtVariableManager vmanager = CCorePlugin.getDefault().getCdtVariableManager(); |
| builderCWD = vmanager.resolveValue(builderCWD, "", null, currentCfgDescription); //$NON-NLS-1$ |
| } catch (CdtVariableException e) { |
| ManagedBuilderCorePlugin.log(e); |
| } |
| if (builderCWD != null && !builderCWD.isEmpty()) { |
| buildDirURI = org.eclipse.core.filesystem.URIUtil.toURI(builderCWD); |
| } |
| } |
| } |
| |
| // try directory of the current project |
| if (buildDirURI == null && currentProject != null) { |
| buildDirURI = currentProject.getLocationURI(); |
| } |
| |
| // try parent folder of the resource |
| if (buildDirURI == null && currentResource != null) { |
| IContainer container; |
| if (currentResource instanceof IContainer) { |
| container = (IContainer) currentResource; |
| } else { |
| container = currentResource.getParent(); |
| } |
| buildDirURI = container.getLocationURI(); |
| } |
| return buildDirURI; |
| } |
| |
| /** |
| * Sets language settings entries for current configuration description, current resource |
| * and current language ID. |
| * |
| * @param entries - language settings entries to set. |
| */ |
| protected void setSettingEntries(List<? extends ICLanguageSettingEntry> entries) { |
| setSettingEntries(currentCfgDescription, currentResource, currentLanguageId, entries); |
| } |
| |
| /** |
| * Determine a language associated with the resource. |
| * |
| * @return language ID for the resource. |
| */ |
| protected String determineLanguage() { |
| IResource rc = currentResource; |
| if (rc == null && currentProject != null && parsedResourceName != null) { |
| String fileName = new Path(parsedResourceName).lastSegment().toString(); |
| // use handle; resource does not need to exist |
| rc = currentProject.getFile("__" + fileName); //$NON-NLS-1$ |
| } |
| |
| if (rc == null) |
| return null; |
| |
| List<String> languageIds = LanguageSettingsManager.getLanguages(rc, currentCfgDescription); |
| if (languageIds.isEmpty()) |
| return null; |
| |
| return languageIds.get(0); |
| } |
| |
| /** |
| * Determine if the language is in scope of the provider. |
| * |
| * @param languageId - language ID. |
| * @return {@code true} if the language is in scope, {@code false } otherwise. |
| */ |
| protected boolean isLanguageInScope(String languageId) { |
| List<String> languageIds = getLanguageScope(); |
| return languageIds == null || languageIds.contains(languageId); |
| } |
| |
| /** |
| * Find file resource in the workspace for a given URI with a preference for the resource |
| * to reside in the given project. |
| */ |
| private IResource findFileForLocationURI(URI uri, IProject preferredProject, boolean checkExistence) { |
| if (!uri.isAbsolute()) { |
| // IWorkspaceRoot.findFilesForLocationURI(URI) below requires an absolute URI |
| // therefore we haven't/aren't going to find the file based on this URI. |
| return null; |
| } |
| IResource sourceFile = null; |
| |
| IResource[] resources = workspaceRootFindFilesForLocationURICache.computeIfAbsent(uri, |
| key -> ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(key)); |
| for (IResource rc : resources) { |
| if (!checkExistence || rc.isAccessible()) { |
| if (rc.getProject().equals(preferredProject)) { |
| sourceFile = rc; |
| break; |
| } |
| if (sourceFile == null) { |
| sourceFile = rc; |
| } |
| } |
| } |
| return sourceFile; |
| } |
| |
| /** |
| * Return a resource in workspace corresponding the given folder {@link URI} preferable residing in |
| * the provided project. |
| */ |
| private IResource findContainerForLocationURI(URI uri, IProject preferredProject, boolean checkExistence) { |
| IResource resource = null; |
| |
| IResource[] resources = workspaceRootFindContainersForLocationURICache.computeIfAbsent(uri, |
| key -> ResourcesPlugin.getWorkspace().getRoot().findContainersForLocationURI(key)); |
| for (IResource rc : resources) { |
| if ((rc instanceof IProject || rc instanceof IFolder) && (!checkExistence || rc.isAccessible())) { // treat IWorkspaceRoot as non-workspace path |
| if (rc.getProject().equals(preferredProject)) { |
| resource = rc; |
| break; |
| } |
| if (resource == null) { |
| resource = rc; // to be deterministic the first qualified resource has preference |
| } |
| } |
| } |
| return resource; |
| } |
| |
| /** |
| * Determine resource in the workspace corresponding to the parsed resource name. |
| */ |
| private IResource findResource(String parsedResourceName) { |
| if (parsedResourceName == null || parsedResourceName.isEmpty()) { |
| return null; |
| } |
| |
| IResource sourceFile = null; |
| |
| // try ErrorParserManager |
| if (cwdTracker instanceof ErrorParserManager) { |
| sourceFile = ((ErrorParserManager) cwdTracker).findFileName(parsedResourceName); |
| } |
| |
| // try to find absolute path in the workspace |
| Path parsedPath = new Path(parsedResourceName); |
| if (sourceFile == null && parsedPath.isAbsolute()) { |
| // It will often happen that the file will be under the project and in the local file system, so check there first. |
| IPath projectLocation = currentProject != null ? currentProject.getLocation() : null; |
| if (projectLocation != null) { |
| IPath relativePath = parsedPath.makeRelativeTo(projectLocation); |
| if (!relativePath.equals(parsedPath)) { |
| IFile file = currentProject.getFile(relativePath); |
| if (file.isAccessible()) { |
| return file; |
| } |
| } |
| } |
| |
| URI uri = org.eclipse.core.filesystem.URIUtil.toURI(parsedResourceName); |
| sourceFile = findFileForLocationURI(uri, currentProject, /*checkExistence*/ true); |
| } |
| |
| // try last known current working directory from build output |
| if (sourceFile == null && cwdTracker != null) { |
| URI cwdURI = cwdTracker.getWorkingDirectoryURI(); |
| if (cwdURI != null) { |
| URI uri = efsProvider.append(cwdURI, parsedResourceName); |
| sourceFile = findFileForLocationURI(uri, currentProject, /*checkExistence*/ true); |
| } |
| } |
| |
| // try path relative to build dir from configuration |
| if (sourceFile == null && currentCfgDescription != null) { |
| IPath builderCWD = currentCfgDescription.getBuildSetting().getBuilderCWD(); |
| if (builderCWD != null) { |
| String strBuilderCWD = builderCWD.toString(); |
| try { |
| ICdtVariableManager varManager = CCorePlugin.getDefault().getCdtVariableManager(); |
| strBuilderCWD = varManager.resolveValue(strBuilderCWD, "", null, currentCfgDescription); //$NON-NLS-1$ |
| } catch (Exception e) { |
| @SuppressWarnings("nls") |
| String msg = "Exception trying to resolve value [" + strBuilderCWD + "]"; |
| ManagedBuilderCorePlugin.log(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.PLUGIN_ID, msg, e)); |
| } |
| builderCWD = new Path(strBuilderCWD); |
| |
| IPath path = builderCWD.append(parsedResourceName); |
| URI uri = org.eclipse.core.filesystem.URIUtil.toURI(path); |
| sourceFile = findFileForLocationURI(uri, currentProject, /*checkExistence*/ true); |
| } |
| } |
| |
| // try path relative to the project |
| if (sourceFile == null && currentProject != null) { |
| sourceFile = currentProject.findMember(parsedResourceName); |
| } |
| |
| return sourceFile; |
| } |
| |
| /** |
| * Find base location of the file, i.e. location of the directory which |
| * results from removing trailing relativeFileName from fileURI or |
| * {@code null} if fileURI doesn't represent relativeFileName. |
| */ |
| private static URI findBaseLocationURI(URI fileURI, String relativeFileName) { |
| URI cwdURI = null; |
| String path = fileURI.getPath(); |
| |
| String[] segments = relativeFileName.split("[/\\\\]"); //$NON-NLS-1$ |
| |
| // start removing segments from the end of the path |
| for (int i = segments.length - 1; i >= 0; i--) { |
| String lastSegment = segments[i]; |
| if (lastSegment.length() > 0 && !lastSegment.equals(".")) { //$NON-NLS-1$ |
| if (lastSegment.equals("..")) { //$NON-NLS-1$ |
| // navigating ".." in the other direction is ambiguous, bailing out |
| return null; |
| } else { |
| if (path.endsWith("/" + lastSegment)) { //$NON-NLS-1$ |
| int pos = path.lastIndexOf("/" + lastSegment); //$NON-NLS-1$ |
| path = path.substring(0, pos); |
| continue; |
| } else { |
| // ouch, relativeFileName does not match fileURI, bailing out |
| return null; |
| } |
| } |
| } |
| } |
| |
| try { |
| cwdURI = new URI(fileURI.getScheme(), fileURI.getUserInfo(), fileURI.getHost(), fileURI.getPort(), |
| path + '/', fileURI.getQuery(), fileURI.getFragment()); |
| } catch (URISyntaxException e) { |
| // It should be valid URI here or something is really wrong |
| ManagedBuilderCorePlugin.log(e); |
| } |
| |
| return cwdURI; |
| } |
| |
| /** |
| * The manipulations here are done to resolve problems such as "../" navigation for symbolic links where |
| * "link/.." cannot be collapsed as it must follow the real file-system path. {@link java.io.File#getCanonicalPath()} |
| * deals with that correctly but {@link Path} or {@link URI} try to normalize the path which would be incorrect here. |
| * Another issue being resolved here is fixing drive letters in URI syntax. |
| */ |
| private static URI resolvePathFromBaseLocation(String pathStr0, IPath baseLocation) { |
| String pathStr = pathStr0; |
| if (baseLocation != null && !baseLocation.isEmpty()) { |
| pathStr = pathStr.replace(File.separatorChar, '/'); |
| String device = new Path(pathStr).getDevice(); |
| if (device == null || device.equals(baseLocation.getDevice())) { |
| if (device != null && device.length() > 0) { |
| pathStr = pathStr.substring(device.length()); |
| } |
| |
| baseLocation = baseLocation.addTrailingSeparator(); |
| if (pathStr.startsWith("/")) { //$NON-NLS-1$ |
| pathStr = pathStr.substring(1); |
| } |
| pathStr = baseLocation.toString() + pathStr; |
| } |
| } |
| |
| try { |
| File file = new File(pathStr); |
| file = file.getCanonicalFile(); |
| URI uri = file.toURI(); |
| if (file.exists()) { |
| return uri; |
| } |
| |
| IPath path0 = new Path(pathStr0); |
| if (!path0.isAbsolute()) { |
| return uri; |
| } |
| |
| String device = path0.getDevice(); |
| if (device == null || device.isEmpty()) { |
| // Avoid spurious adding of drive letters on Windows |
| pathStr = path0.setDevice(null).toString(); |
| } else { |
| // On Windows "C:/folder/" -> "/C:/folder/" |
| if (pathStr.charAt(0) != IPath.SEPARATOR) { |
| pathStr = IPath.SEPARATOR + pathStr; |
| } |
| } |
| |
| return new URI(uri.getScheme(), uri.getAuthority(), pathStr, uri.getQuery(), uri.getFragment()); |
| |
| } catch (Exception e) { |
| // if error will leave it as is |
| ManagedBuilderCorePlugin.log(e); |
| } |
| |
| return org.eclipse.core.filesystem.URIUtil.toURI(pathStr); |
| } |
| |
| /** |
| * Determine URI on the local file-system considering possible mapping. |
| * |
| * @param pathStr - path to the resource, can be absolute or relative |
| * @param baseURI - base {@link URI} where path to the resource is rooted |
| * @return {@link URI} of the resource |
| */ |
| private URI determineMappedURI(String pathStr, URI baseURI) { |
| URI uri = null; |
| |
| if (baseURI == null) { |
| if (new Path(pathStr).isAbsolute()) { |
| uri = resolvePathFromBaseLocation(pathStr, Path.ROOT); |
| } |
| } else if (baseURI.getScheme().equals(EFS.SCHEME_FILE)) { |
| // location on the local file-system |
| IPath baseLocation = org.eclipse.core.filesystem.URIUtil.toPath(baseURI); |
| // careful not to use Path here but 'pathStr' as String as we want to properly navigate symlinks |
| uri = resolvePathFromBaseLocation(pathStr, baseLocation); |
| } else { |
| // location on a remote file-system |
| IPath path = new Path(pathStr); // use canonicalized path here, in particular replace all '\' with '/' for Windows paths |
| URI remoteUri = efsProvider.append(baseURI, path.toString()); |
| if (remoteUri != null) { |
| String localPath = efsProvider.getMappedPath(remoteUri); |
| if (localPath != null) { |
| uri = org.eclipse.core.filesystem.URIUtil.toURI(localPath); |
| } |
| } |
| } |
| |
| if (uri == null) { |
| // if everything fails just wrap string to URI |
| uri = org.eclipse.core.filesystem.URIUtil.toURI(pathStr); |
| } |
| return uri; |
| } |
| |
| /** |
| * Find all resources in the project which might be represented by relative path passed. |
| */ |
| private List<IResource> findPathInProject(IPath path, IProject project) { |
| LRUCache<IPath, List<IResource>> cache = findPathInProjectCache.computeIfAbsent(project, |
| key -> new LRUCache<>(FIND_RESOURCES_CACHE_SIZE)); |
| return cache.computeIfAbsent(path, key -> findPathInFolder(path, project)); |
| } |
| |
| /** |
| * Find all resources in the folder which might be represented by relative path passed. |
| */ |
| private static List<IResource> findPathInFolder(IPath path, IContainer folder) { |
| List<IResource> paths = new ArrayList<>(); |
| IResource resource = folder.findMember(path); |
| if (resource != null) { |
| paths.add(resource); |
| } |
| |
| try { |
| for (IResource res : folder.members()) { |
| if (res instanceof IContainer) { |
| paths.addAll(findPathInFolder(path, (IContainer) res)); |
| } |
| } |
| } catch (CoreException e) { |
| // ignore |
| } |
| |
| return paths; |
| } |
| |
| /** |
| * Determine which resource in workspace is the best fit to parsedName passed. |
| */ |
| private IResource findBestFitInWorkspace(String parsedName) { |
| Set<String> referencedProjectsNames = new LinkedHashSet<>(); |
| if (currentCfgDescription != null) { |
| Map<String, String> refs = currentCfgDescription.getReferenceInfo(); |
| referencedProjectsNames.addAll(refs.keySet()); |
| } |
| |
| IPath path = new Path(parsedName); |
| if (path.equals(new Path(".")) || path.equals(new Path(".."))) { //$NON-NLS-1$ //$NON-NLS-2$ |
| return null; |
| } |
| |
| // prefer current project |
| if (currentProject != null) { |
| List<IResource> result = findPathInProject(path, currentProject); |
| int size = result.size(); |
| if (size == 1) { // found the one |
| return result.get(0); |
| } else if (size > 1) { // ambiguous |
| return null; |
| } |
| } |
| |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| |
| // then prefer referenced projects |
| if (referencedProjectsNames.size() > 0) { |
| IResource rc = null; |
| for (String prjName : referencedProjectsNames) { |
| IProject prj = root.getProject(prjName); |
| if (prj.isOpen()) { |
| List<IResource> result = findPathInProject(path, prj); |
| int size = result.size(); |
| if (size == 1 && rc == null) { |
| rc = result.get(0); |
| } else if (size > 0) { |
| // ambiguous |
| rc = null; |
| break; |
| } |
| } |
| } |
| if (rc != null) { |
| return rc; |
| } |
| } |
| |
| // then check all other projects in workspace |
| IProject[] projects = root.getProjects(); |
| if (projects.length > 0) { |
| IResource rc = null; |
| for (IProject prj : projects) { |
| if (!prj.equals(currentProject) && !referencedProjectsNames.contains(prj.getName()) && prj.isOpen()) { |
| List<IResource> result = findPathInProject(path, prj); |
| int size = result.size(); |
| if (size == 1 && rc == null) { |
| rc = result.get(0); |
| } else if (size > 0) { |
| // ambiguous |
| rc = null; |
| break; |
| } |
| } |
| } |
| if (rc != null) { |
| return rc; |
| } |
| } |
| |
| // not found or ambiguous |
| return null; |
| } |
| |
| /** |
| * Get location on the local file-system considering possible mapping by EFS provider. See {@link EFSExtensionManager}. |
| */ |
| private IPath getFilesystemLocation(URI uri) { |
| if (uri == null) |
| return null; |
| |
| String pathStr = efsProvider.getMappedPath(uri); |
| uri = org.eclipse.core.filesystem.URIUtil.toURI(pathStr); |
| |
| if (uri != null && uri.isAbsolute()) { |
| try { |
| File file = new java.io.File(uri); |
| String canonicalPathStr = file.getCanonicalPath(); |
| if (new Path(pathStr).getDevice() == null) { |
| return new Path(canonicalPathStr).setDevice(null); |
| } |
| return new Path(canonicalPathStr); |
| } catch (Exception e) { |
| ManagedBuilderCorePlugin.log(e); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Resolve and create language settings path entry. |
| */ |
| private ICLanguageSettingEntry createResolvedPathEntry(AbstractOptionParser optionParser, String parsedPath, |
| int flag, URI baseURI) { |
| URI uri = determineMappedURI(parsedPath, baseURI); |
| boolean isRelative = !new Path(parsedPath).isAbsolute(); |
| // is mapped something that is not a project root |
| boolean isRemapped = baseURI != null && currentProject != null |
| && !baseURI.equals(currentProject.getLocationURI()); |
| boolean presentAsRelative = isRelative || isRemapped; |
| |
| ICLanguageSettingEntry entry = resolvePathEntryInWorkspace(optionParser, uri, flag, presentAsRelative); |
| if (entry != null) { |
| return entry; |
| } |
| entry = resolvePathEntryInFilesystem(optionParser, uri, flag); |
| if (entry != null) { |
| return entry; |
| } |
| entry = resolvePathEntryInWorkspaceAsBestFit(optionParser, parsedPath, flag, presentAsRelative); |
| if (entry != null) { |
| return entry; |
| } |
| entry = resolvePathEntryInWorkspaceToNonexistingResource(optionParser, uri, flag, presentAsRelative); |
| if (entry != null) { |
| return entry; |
| } |
| entry = resolvePathEntryInFilesystemToNonExistingResource(optionParser, uri, flag); |
| if (entry != null) { |
| return entry; |
| } |
| return optionParser.createEntry(parsedPath, parsedPath, flag); |
| } |
| |
| /** |
| * Create a language settings entry for a given resource. |
| * This will represent relative path using CDT variable ${ProjName}. |
| */ |
| private ICLanguageSettingEntry createPathEntry(AbstractOptionParser optionParser, IResource rc, boolean isRelative, |
| int flag) { |
| String path; |
| if (isRelative && rc.getProject().equals(currentProject)) { |
| path = PROJ_NAME_PREFIX + rc.getFullPath().removeFirstSegments(1); |
| flag = flag | ICSettingEntry.VALUE_WORKSPACE_PATH; |
| } else { |
| path = rc.getFullPath().toString(); |
| flag = flag | ICSettingEntry.VALUE_WORKSPACE_PATH | ICSettingEntry.RESOLVED; |
| } |
| return optionParser.createEntry(path, path, flag); |
| } |
| |
| /** |
| * Find an existing resource in the workspace and create a language settings entry for it. |
| */ |
| private ICLanguageSettingEntry resolvePathEntryInWorkspace(AbstractOptionParser optionParser, URI uri, int flag, |
| boolean isRelative) { |
| if (uri != null && uri.isAbsolute()) { |
| IResource rc = null; |
| if (optionParser.isForFolder()) { |
| rc = findContainerForLocationURI(uri, currentProject, /*checkExistence*/ true); |
| } else if (optionParser.isForFile()) { |
| rc = findFileForLocationURI(uri, currentProject, /*checkExistence*/ true); |
| } |
| if (rc != null) { |
| return createPathEntry(optionParser, rc, isRelative, flag); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Find a resource on the file-system and create a language settings entry for it. |
| */ |
| private ICLanguageSettingEntry resolvePathEntryInFilesystem(AbstractOptionParser optionParser, URI uri, int flag) { |
| IPath location = getFilesystemLocation(uri); |
| if (location != null) { |
| String loc = location.toString(); |
| if (new File(loc).exists()) { |
| return optionParser.createEntry(loc, loc, flag); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Find a best fit for the resource in the workspace and create a language settings entry for it. |
| */ |
| private ICLanguageSettingEntry resolvePathEntryInWorkspaceAsBestFit(AbstractOptionParser optionParser, |
| String parsedPath, int flag, boolean isRelative) { |
| IResource rc = findBestFitInWorkspace(parsedPath); |
| if (rc != null) { |
| return createPathEntry(optionParser, rc, isRelative, flag); |
| } |
| return null; |
| } |
| |
| /** |
| * Try to map a resource in the workspace even if it does not exist and create a language settings entry for it. |
| */ |
| private ICLanguageSettingEntry resolvePathEntryInWorkspaceToNonexistingResource(AbstractOptionParser optionParser, |
| URI uri, int flag, boolean isRelative) { |
| if (uri != null && uri.isAbsolute()) { |
| IResource rc = null; |
| if (optionParser.isForFolder()) { |
| rc = findContainerForLocationURI(uri, currentProject, /*checkExistence*/ false); |
| } else if (optionParser.isForFile()) { |
| rc = findFileForLocationURI(uri, currentProject, /*checkExistence*/ false); |
| } |
| if (rc != null) { |
| return createPathEntry(optionParser, rc, isRelative, flag); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Try to map a resource on the file-system even if it does not exist and create a language settings entry for it. |
| */ |
| private ICLanguageSettingEntry resolvePathEntryInFilesystemToNonExistingResource(AbstractOptionParser optionParser, |
| URI uri, int flag) { |
| IPath location = getFilesystemLocation(uri); |
| if (location != null) { |
| return optionParser.createEntry(location.toString(), location.toString(), flag); |
| } |
| return null; |
| } |
| |
| /** |
| * Count how many groups are present in regular expression. |
| * The implementation is simplistic but should be sufficient for the cause. |
| * |
| * @param str - regular expression to count the groups. |
| * @return number of the groups (groups are enclosed in round brackets) present. |
| */ |
| protected static int countGroups(String str) { |
| @SuppressWarnings("nls") |
| int count = str.replaceAll("[^\\(]", "").length(); |
| return count; |
| } |
| |
| /** |
| * Helper method to construct logical "or" to be used inside regular expressions. |
| */ |
| @SuppressWarnings("nls") |
| private static String expressionLogicalOr(Set<String> fileExts) { |
| String pattern = "("; |
| for (String ext : fileExts) { |
| if (pattern.length() != 1) |
| pattern += "|"; |
| pattern += "(" + Pattern.quote(ext) + ")"; |
| ext = ext.toUpperCase(); |
| if (!fileExts.contains(ext)) { |
| pattern += "|(" + Pattern.quote(ext) + ")"; |
| } |
| } |
| pattern += ")"; |
| return pattern; |
| } |
| |
| /** |
| * Construct regular expression to find any file extension for C or C++. |
| * Returns expression shaped in form of "((cpp)|(c++)|(c))". |
| * |
| * @return regular expression for searching C/C++ file extensions. |
| */ |
| protected String getPatternFileExtensions() { |
| IContentTypeManager manager = Platform.getContentTypeManager(); |
| |
| Set<String> fileExts = new HashSet<>(); |
| |
| IContentType contentTypeCpp = manager.getContentType(CCorePlugin.CONTENT_TYPE_CXXSOURCE); |
| fileExts.addAll(Arrays.asList(contentTypeCpp.getFileSpecs(IContentType.FILE_EXTENSION_SPEC))); |
| |
| IContentType contentTypeC = manager.getContentType(CCorePlugin.CONTENT_TYPE_CSOURCE); |
| fileExts.addAll(Arrays.asList(contentTypeC.getFileSpecs(IContentType.FILE_EXTENSION_SPEC))); |
| |
| String pattern = expressionLogicalOr(fileExts); |
| |
| return pattern; |
| } |
| |
| /** |
| * This {@link EFSExtensionProvider} is capable to translate EFS paths to and from local |
| * file-system. Added mostly for Cygwin translations. |
| * |
| * This usage of {@link EFSExtensionProvider} is somewhat a misnomer. This provider is not |
| * an "extension" provider but rather a wrapper on {@link EFSExtensionManager} which in fact |
| * will use genuine {@link EFSExtensionProvider}s defined as extensions. |
| * |
| * @since 8.2 |
| */ |
| protected EFSExtensionProvider getEFSProvider() { |
| return efsProviderDefault; |
| } |
| |
| @Override |
| public Element serializeAttributes(Element parentElement) { |
| Element elementProvider = super.serializeAttributes(parentElement); |
| elementProvider.setAttribute(ATTR_KEEP_RELATIVE_PATHS, Boolean.toString(!isResolvingPaths)); |
| return elementProvider; |
| } |
| |
| @Override |
| public void loadAttributes(Element providerNode) { |
| super.loadAttributes(providerNode); |
| |
| String expandRelativePathsValue = XmlUtil.determineAttributeValue(providerNode, ATTR_KEEP_RELATIVE_PATHS); |
| if (expandRelativePathsValue != null) |
| isResolvingPaths = !Boolean.parseBoolean(expandRelativePathsValue); |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = super.hashCode(); |
| result = prime * result + (isResolvingPaths ? 1231 : 1237); |
| 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; |
| AbstractLanguageSettingsOutputScanner other = (AbstractLanguageSettingsOutputScanner) obj; |
| if (isResolvingPaths != other.isResolvingPaths) |
| return false; |
| return true; |
| } |
| |
| } |