/******************************************************************************* * Copyright (c) 2013, 2015 Google, Inc 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: * Sergey Prigogin (Google) - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.internal.corext.codemanipulation; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.cdt.core.CCorePreferenceConstants; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.parser.IScannerInfo; import org.eclipse.cdt.utils.PathUtil; import org.eclipse.cdt.internal.core.parser.scanner.CPreprocessor; import org.eclipse.cdt.internal.core.parser.scanner.IncludeSearchPath; import org.eclipse.cdt.internal.core.parser.scanner.IncludeSearchPathElement; import org.eclipse.cdt.internal.core.parser.scanner.ScannerUtility; import org.eclipse.cdt.internal.core.resources.ResourceLookup; import org.eclipse.cdt.internal.ui.editor.SourceHeaderPartnerFinder; import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeGroupStyle; import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeGroupStyle.IncludeKind; import org.eclipse.cdt.internal.ui.refactoring.includes.IncludePreferences; public class InclusionContext { private static final IPath UNRESOLVED_INCLUDE = Path.EMPTY; private final ITranslationUnit fTu; private final IProject fProject; private final IPath fCurrentDirectory; private final IncludeSearchPath fIncludeSearchPath; private final Map<IncludeInfo, IPath> fIncludeResolutionCache; private final Map<IPath, IncludeInfo> fInverseIncludeResolutionCache; private final IncludePreferences fPreferences; private String fSourceContents; private String fLineDelimiter; private Pattern fKeepPragmaPattern; private IPath fTuLocation; public InclusionContext(ITranslationUnit tu) { fTu = tu; fTuLocation = fTu.getLocation(); ICProject cProject = fTu.getCProject(); fProject = cProject.getProject(); fCurrentDirectory = fTuLocation == null ? null : fTuLocation.removeLastSegments(1); IScannerInfo scannerInfo = fTu.getScannerInfo(true); fIncludeSearchPath = CPreprocessor.configureIncludeSearchPath(fCurrentDirectory.toFile(), scannerInfo); fIncludeResolutionCache = new HashMap<>(); fInverseIncludeResolutionCache = new HashMap<>(); fPreferences = new IncludePreferences(cProject); } public final ITranslationUnit getTranslationUnit() { return fTu; } public final IProject getProject() { return fProject; } public final IPath getCurrentDirectory() { return fCurrentDirectory; } public final IncludePreferences getPreferences() { return fPreferences; } public IPath resolveInclude(IncludeInfo include) { IPath path = fIncludeResolutionCache.get(include); if (path == null) { String directory = fCurrentDirectory == null ? null : fCurrentDirectory.toOSString(); String filePath = CPreprocessor.getAbsoluteInclusionPath(include.getName(), directory); if (filePath != null) { path = new Path(filePath); } else if (!include.isSystem() && !fIncludeSearchPath.isInhibitUseOfCurrentFileDirectory()) { // Check to see if we find a match in the current directory. filePath = ScannerUtility.createReconciledPath(directory, include.getName()); if (fileExists(filePath)) { path = new Path(filePath); } } if (path == null) { for (IncludeSearchPathElement pathElement : fIncludeSearchPath.getElements()) { if (include.isSystem() && pathElement.isForQuoteIncludesOnly()) continue; filePath = pathElement.getLocation(include.getName()); if (fileExists(filePath)) { path = new Path(filePath); break; } } } if (path == null) path = UNRESOLVED_INCLUDE; fIncludeResolutionCache.put(include, path); fInverseIncludeResolutionCache.put(path, include); } return path == UNRESOLVED_INCLUDE ? null : path; } /** * Returns the include directive that resolves to the given header file, or {@code null} if * the file is not on the include search path. Current directory is not considered to be a part * of the include path by this method. */ public IncludeInfo getIncludeForHeaderFile(IPath fullPath) { IncludeInfo include = fInverseIncludeResolutionCache.get(fullPath); if (include != null) return include; String headerLocation = fullPath.toOSString(); String shortestInclude = null; boolean isSystem = false; for (IncludeSearchPathElement pathElement : fIncludeSearchPath.getElements()) { String includeDirective = pathElement.getIncludeDirective(headerLocation); if (includeDirective != null && (shortestInclude == null || shortestInclude.length() > includeDirective.length())) { shortestInclude = includeDirective; isSystem = !pathElement.isForQuoteIncludesOnly(); } } if (shortestInclude == null) { if (fIncludeSearchPath.isInhibitUseOfCurrentFileDirectory() || fCurrentDirectory == null || !fCurrentDirectory.isPrefixOf(fullPath)) { return null; } shortestInclude = fullPath.setDevice(null).removeFirstSegments(fCurrentDirectory.segmentCount()).toString(); } include = new IncludeInfo(shortestInclude, isSystem); // Don't put an include to fullPath to fIncludeResolutionCache since it may be wrong // if the header was included by #include_next. fInverseIncludeResolutionCache.put(fullPath, include); return include; } /** * Returns the include directive that resolves to the given header file, or {@code null} if * the file is not on the include search path. Current directory is not considered to be a part * of the include path by this method. */ public IncludeInfo getIncludeForHeaderFile(IPath fullPath, boolean isSystem) { IncludeInfo include = fInverseIncludeResolutionCache.get(fullPath); if (include != null) return include; String headerLocation = fullPath.toOSString(); String shortestInclude = null; for (IncludeSearchPathElement pathElement : fIncludeSearchPath.getElements()) { if (isSystem && pathElement.isForQuoteIncludesOnly()) continue; String includeDirective = pathElement.getIncludeDirective(headerLocation); if (includeDirective != null && (shortestInclude == null || shortestInclude.length() > includeDirective.length())) { shortestInclude = includeDirective; } } if (shortestInclude == null) { if (fIncludeSearchPath.isInhibitUseOfCurrentFileDirectory() || fCurrentDirectory == null || !fCurrentDirectory.isPrefixOf(fullPath)) { return null; } shortestInclude = fullPath.setDevice(null).removeFirstSegments(fCurrentDirectory.segmentCount()).toString(); } include = new IncludeInfo(shortestInclude, isSystem); // Don't put an include to fullPath to fIncludeResolutionCache since it may be wrong // if the header was included by #include_next. fInverseIncludeResolutionCache.put(fullPath, include); return include; } public IncludeGroupStyle getIncludeStyle(IPath headerPath) { IncludeKind includeKind; IncludeInfo includeInfo = getIncludeForHeaderFile(headerPath); if (includeInfo != null && includeInfo.isSystem()) { if (headerPath.getFileExtension() == null) { includeKind = IncludeKind.SYSTEM_WITHOUT_EXTENSION; } else { includeKind = IncludeKind.SYSTEM_WITH_EXTENSION; } } else if (isPartnerFile(headerPath)) { includeKind = IncludeKind.PARTNER; } else { IPath dir = getCurrentDirectory(); if (dir.isPrefixOf(headerPath)) { if (headerPath.segmentCount() == dir.segmentCount() + 1) { includeKind = IncludeKind.IN_SAME_FOLDER; } else { includeKind = IncludeKind.IN_SUBFOLDER; } } else { IFile[] files = ResourceLookup.findFilesForLocation(headerPath); if (files.length == 0) { includeKind = IncludeKind.EXTERNAL; } else { IProject project = getProject(); includeKind = IncludeKind.IN_OTHER_PROJECT; for (IFile file : files) { if (file.getProject().equals(project)) { includeKind = IncludeKind.IN_SAME_PROJECT; break; } } } } } return fPreferences.includeStyles.get(includeKind); } public IncludeGroupStyle getIncludeStyle(IncludeInfo includeInfo) { IncludeKind includeKind; IPath path = Path.fromPortableString(includeInfo.getName()); if (includeInfo.isSystem()) { if (path.getFileExtension() == null) { includeKind = IncludeKind.SYSTEM_WITHOUT_EXTENSION; } else { includeKind = IncludeKind.SYSTEM_WITH_EXTENSION; } } else if (isPartnerFile(path)) { includeKind = IncludeKind.PARTNER; } else { includeKind = IncludeKind.EXTERNAL; } return fPreferences.includeStyles.get(includeKind); } private static boolean fileExists(String absolutePath) { return new File(absolutePath).exists(); } /** * Checks if the given path points to a partner header of the current translation unit. * A header is considered a partner if its name without extension is the same as the name of * the translation unit, or the name of the translation unit differs by one of the suffixes * used for test files. */ public boolean isPartnerFile(IPath path) { return SourceHeaderPartnerFinder.isPartnerFile(getTranslationUnitLocation(), path, fPreferences.partnerFileSuffixes); } public IncludeInfo createIncludeInfo(IPath header, IncludeGroupStyle style) { String name = null; if (style.isRelativePath()) { name = getRelativePath(header); } if (name == null) { IncludeInfo includeInfo = getIncludeForHeaderFile(header); if (includeInfo != null) { name = includeInfo.getName(); } else { name = getRelativePath(header); } if (name == null) { name = header.toPortableString(); // Last resort. } } return new IncludeInfo(name, style.isAngleBrackets()); } private String getRelativePath(IPath header) { IPath relativePath = PathUtil.makeRelativePath(header, getCurrentDirectory()); if (relativePath == null) return null; return relativePath.toString(); } public String getSourceContents() { if (fSourceContents == null) { fSourceContents = new String(fTu.getContents()); } return fSourceContents; } public String getLineDelimiter() { if (fLineDelimiter == null) { try { fLineDelimiter = StubUtility.getLineDelimiterUsed(fTu); } catch (CModelException e) { fLineDelimiter = System.getProperty("line.separator", "\n"); //$NON-NLS-1$//$NON-NLS-2$ } } return fLineDelimiter; } public Pattern getKeepPragmaPattern() { if (fKeepPragmaPattern == null) { String keepPattern = CCorePreferenceConstants.getPreference( CCorePreferenceConstants.INCLUDE_KEEP_PATTERN, fProject, CCorePreferenceConstants.DEFAULT_INCLUDE_KEEP_PATTERN); try { fKeepPragmaPattern = Pattern.compile(keepPattern); } catch (PatternSyntaxException e) { fKeepPragmaPattern = Pattern.compile(CCorePreferenceConstants.DEFAULT_INCLUDE_KEEP_PATTERN); } } return fKeepPragmaPattern; } /** * Sets the effective translation unit location that overrides the default value obtained by * calling {@code getTranslationUnit().getLocation()}. * * @param location the file system location to set */ public void setTranslationUnitLocation(IPath location) { this.fTuLocation = location; } /** * Returns the effective translation unit location. */ public IPath getTranslationUnitLocation() { return fTuLocation; } }