/******************************************************************************* * Copyright (c) 2012, 2014 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.ui.refactoring.includes; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.dom.ast.IASTComment; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorPragmaStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.index.IIndexFile; import org.eclipse.cdt.core.index.IIndexFileLocation; import org.eclipse.cdt.core.index.IndexLocationFactory; import org.eclipse.cdt.core.parser.Keywords; import org.eclipse.cdt.core.parser.util.CharArrayIntMap; import org.eclipse.cdt.core.parser.util.CharArrayUtils; import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap; import org.eclipse.cdt.internal.core.dom.rewrite.util.ASTNodes; import org.eclipse.cdt.internal.core.parser.scanner.CharArray; import org.eclipse.cdt.internal.core.parser.scanner.IncludeGuardDetection; import org.eclipse.cdt.internal.core.parser.scanner.Lexer.LexerOptions; import org.eclipse.cdt.internal.core.util.TextUtil; import org.eclipse.cdt.internal.corext.codemanipulation.IncludeInfo; import org.eclipse.cdt.internal.corext.codemanipulation.InclusionContext; import org.eclipse.cdt.internal.corext.codemanipulation.StyledInclude; public class IncludeUtil { /** Not instantiatable. All methods are static. */ private IncludeUtil() {} /** * Checks if a file is a source file (.c, .cpp, .cc, etc). Header files are not considered * source files. * @return Returns {@code true} if the the file is a source file. */ public static boolean isSource(IIndexFile file, IProject project) throws CoreException { return isSource(getPath(file), project); } /** * Checks if a file is a source file (.c, .cpp, .cc, etc). Header files are not considered * source files. * @return Returns {@code true} if the the file is a source file. */ public static boolean isSource(String filename, IProject project) { IContentType ct= CCorePlugin.getContentType(project, filename); if (ct != null) { String id = ct.getId(); if (CCorePlugin.CONTENT_TYPE_CSOURCE.equals(id) || CCorePlugin.CONTENT_TYPE_CXXSOURCE.equals(id)) { return true; } } return false; } /** * Returns the path of the given index file. * @param file The index file. * @return The path. */ public static String getPath(IIndexFile file) throws CoreException { return getPath(file.getLocation()); } /** * Returns the path of the given index file. * @param fileLocation The index file location. * @return The path. */ public static String getPath(IIndexFileLocation fileLocation) { return IndexLocationFactory.getAbsolutePath(fileLocation).toOSString(); } public static boolean isContainedInRegion(IASTNode node, IRegion region) { return ASTNodes.offset(node) >= region.getOffset() && ASTNodes.endOffset(node) <= region.getOffset() + region.getLength(); } /** * Returns the region containing nothing but include statements located before the first * statement that may depend on includes. * * @param contents the contents of the translation unit * @param ast the AST * @param commentMap comments of the translation unit * @return the include region, possibly empty */ public static IRegion getSafeIncludeReplacementRegion(String contents, IASTTranslationUnit ast, NodeCommentMap commentMap) { int maxSafeOffset = ast.getFileLocation().getNodeLength(); IASTDeclaration[] declarations = ast.getDeclarations(true); if (declarations.length != 0) maxSafeOffset = declarations[0].getFileLocation().getNodeOffset(); boolean topCommentSkipped = false; int includeOffset = -1; int includeEndOffset = -1; int includeGuardStatementsToSkip = getNumberOfIncludeGuardStatementsToSkip(contents, ast); int includeGuardEndOffset = -1; for (IASTPreprocessorStatement statement : ast.getAllPreprocessorStatements()) { if (statement.isPartOfTranslationUnitFile()) { IASTFileLocation fileLocation = statement.getFileLocation(); int offset = fileLocation.getNodeOffset(); if (offset >= maxSafeOffset) break; int endOffset = offset + fileLocation.getNodeLength(); if (includeGuardStatementsToSkip > 0) { --includeGuardStatementsToSkip; includeGuardEndOffset = endOffset; if (!commentMap.getLeadingCommentsForNode(statement).isEmpty()) { topCommentSkipped = true; } } else if (statement instanceof IASTPreprocessorIncludeStatement) { if (includeOffset < 0) includeOffset = offset; includeEndOffset = endOffset; includeGuardStatementsToSkip = 0; // Just in case } else { break; } } } if (includeOffset < 0) { if (includeGuardEndOffset >= 0) { includeOffset = TextUtil.skipToNextLine(contents, includeGuardEndOffset); } else { includeOffset = 0; } if (!topCommentSkipped) { // Skip the first comment block near the top of the file. includeOffset = skipStandaloneCommentBlock(contents, includeOffset, maxSafeOffset, ast.getComments(), commentMap); } includeEndOffset = includeOffset; } else { includeEndOffset = TextUtil.skipToNextLine(contents, includeEndOffset); } return new Region(includeOffset, includeEndOffset - includeOffset); } /** * Returns the include statements within the given region. * * @param existingIncludes the include statements to choose from * @param region the region to select includes within * @param inclusionContext the inclusion context * @return a list of {@link StyledInclude} objects representing the includes */ public static List<StyledInclude> getIncludesInRegion(IASTPreprocessorIncludeStatement[] existingIncludes, IRegion region, InclusionContext inclusionContext) { // Populate a list of existing includes in the include insertion region. List<StyledInclude> includes = new ArrayList<>(); for (IASTPreprocessorIncludeStatement include : existingIncludes) { if (include.isPartOfTranslationUnitFile() && isContainedInRegion(include, region)) { String name = new String(include.getName().getSimpleID()); IncludeInfo includeInfo = new IncludeInfo(name, include.isSystemInclude()); String path = include.getPath(); // An empty path means that the include was not resolved. IPath header = path.isEmpty() ? null : Path.fromOSString(path); IncludeGroupStyle style = header != null ? inclusionContext.getIncludeStyle(header) : inclusionContext.getIncludeStyle(includeInfo); StyledInclude prototype = new StyledInclude(header, includeInfo, style, include); includes.add(prototype); } } return includes; } /** * Searches for the include guard in the file and, if found, returns its value and occurrences * in the file. * * @param contents the contents of the translation unit * @param ast the AST * @param includeGuardPositions the list of include guard occurrences that is populated by * the method * @return the include guard, or {@code null} if not found */ public static String findIncludeGuard(String contents, IASTTranslationUnit ast, List<IRegion> includeGuardPositions) { includeGuardPositions.clear(); IASTPreprocessorStatement[] preprocessorStatements = ast.getAllPreprocessorStatements(); int i = 0; while (true) { if (i >= preprocessorStatements.length) return null; if (preprocessorStatements[i].isPartOfTranslationUnitFile()) break; i++; } IASTPreprocessorStatement statement = preprocessorStatements[i]; int offset = 0; if (isPragmaOnce(statement)) { offset = ASTNodes.endOffset(statement); i++; } char[] guardChars = detectIncludeGuard(contents, offset); if (guardChars == null) return null; String guard = new String(guardChars); int count = 0; IASTPreprocessorStatement lastStatement = null; for (; i < preprocessorStatements.length; i++) { statement = preprocessorStatements[i]; if (statement.isPartOfTranslationUnitFile()) { if (count < 2) { findGuardInRange(contents, guard, ASTNodes.offset(statement), ASTNodes.endOffset(statement), includeGuardPositions); count++; } else { lastStatement = statement; } } } if (lastStatement != null) { findGuardInRange(contents, guard, ASTNodes.offset(lastStatement), contents.length(), includeGuardPositions); } return guard; } private static int getNumberOfIncludeGuardStatementsToSkip(String contents, IASTTranslationUnit ast) { IASTPreprocessorStatement statement = findFirstPreprocessorStatement(ast); if (statement == null) return 0; int num = 0; int offset = 0; if (isPragmaOnce(statement)) { num++; offset = ASTNodes.endOffset(statement); } char[] guard = detectIncludeGuard(contents, offset); if (guard != null) { num += 2; } return num; } private static char[] detectIncludeGuard(String contents, int offset) { char[] contentsChars = contents.toCharArray(); if (offset != 0) contentsChars = Arrays.copyOfRange(contentsChars, offset, contentsChars.length); CharArrayIntMap ppKeywords= new CharArrayIntMap(40, -1); Keywords.addKeywordsPreprocessor(ppKeywords); char[] guardChars = IncludeGuardDetection.detectIncludeGuard( new CharArray(contentsChars), new LexerOptions(), ppKeywords); return guardChars; } private static void findGuardInRange(String contents, String guard, int offset, int endOffset, List<IRegion> includeGuardPositions) { int pos = contents.indexOf(guard, offset); if (pos >= 0 && pos + guard.length() <= endOffset) { includeGuardPositions.add(new Region(pos, guard.length())); } } private static int skipStandaloneCommentBlock(String contents, int offset, int endOffset, IASTComment[] comments, NodeCommentMap commentMap) { Map<IASTComment, IASTNode> inverseLeadingMap = new HashMap<>(); for (Map.Entry<IASTNode, List<IASTComment>> entry : commentMap.getLeadingMap().entrySet()) { IASTNode node = entry.getKey(); if (ASTNodes.offset(node) <= endOffset) { for (IASTComment comment : entry.getValue()) { inverseLeadingMap.put(comment, node); } } } Map<IASTComment, IASTNode> inverseFreestandingMap = new HashMap<>(); for (Map.Entry<IASTNode, List<IASTComment>> entry : commentMap.getFreestandingMap().entrySet()) { IASTNode node = entry.getKey(); if (ASTNodes.endOffset(node) < endOffset) { for (IASTComment comment : entry.getValue()) { inverseFreestandingMap.put(comment, node); } } } for (int i = 0; i < comments.length; i++) { IASTComment comment = comments[i]; int commentOffset = ASTNodes.offset(comment); if (commentOffset >= offset) { if (commentOffset >= endOffset) break; IASTNode node = inverseLeadingMap.get(comment); if (node != null) { List<IASTComment> leadingComments = commentMap.getLeadingMap().get(node); IASTComment previous = leadingComments.get(0); for (int j = 1; j < leadingComments.size(); j++) { comment = leadingComments.get(j); if (ASTNodes.getStartingLineNumber(comment) > ASTNodes.getEndingLineNumber(previous) + 1) return ASTNodes.skipToNextLineAfterNode(contents, previous); previous = comment; } if (ASTNodes.getStartingLineNumber(node) > ASTNodes.getEndingLineNumber(previous) + 1) return ASTNodes.skipToNextLineAfterNode(contents, previous); } node = inverseFreestandingMap.get(comment); if (node != null) { List<IASTComment> freestandingComments = commentMap.getFreestandingMap().get(node); IASTComment previous = freestandingComments.get(0); for (int j = 1; j < freestandingComments.size(); j++) { comment = freestandingComments.get(j); if (ASTNodes.getStartingLineNumber(comment) > ASTNodes.getEndingLineNumber(previous) + 1) return ASTNodes.skipToNextLineAfterNode(contents, previous); previous = comment; } } } } return offset; } private static IASTPreprocessorStatement findFirstPreprocessorStatement(IASTTranslationUnit ast) { for (IASTPreprocessorStatement statement : ast.getAllPreprocessorStatements()) { if (statement.isPartOfTranslationUnitFile()) return statement; } return null; } private static boolean isPragmaOnce(IASTPreprocessorStatement statement) { if (!(statement instanceof IASTPreprocessorPragmaStatement)) return false; return CharArrayUtils.equals(((IASTPreprocessorPragmaStatement) statement).getMessage(), "once"); //$NON-NLS-1$ } }