| /******************************************************************************* |
| * Copyright (c) 2012, 2016 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 |
| * Mathias Kunter |
| *******************************************************************************/ |
| package org.eclipse.cdt.internal.ui.refactoring.includes; |
| |
| import static org.eclipse.cdt.core.index.IndexLocationFactory.getAbsolutePath; |
| import static org.eclipse.cdt.internal.ui.refactoring.includes.IncludeUtil.isContainedInRegion; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.text.edits.DeleteEdit; |
| import org.eclipse.text.edits.InsertEdit; |
| import org.eclipse.text.edits.MultiTextEdit; |
| import org.eclipse.text.edits.ReplaceEdit; |
| |
| import com.ibm.icu.text.Collator; |
| |
| import org.eclipse.cdt.core.dom.IName; |
| import org.eclipse.cdt.core.dom.ast.ASTTypeUtil; |
| import org.eclipse.cdt.core.dom.ast.DOMException; |
| import org.eclipse.cdt.core.dom.ast.EScopeKind; |
| import org.eclipse.cdt.core.dom.ast.IASTComment; |
| import org.eclipse.cdt.core.dom.ast.IASTFileLocation; |
| import org.eclipse.cdt.core.dom.ast.IASTName; |
| import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; |
| import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; |
| import org.eclipse.cdt.core.dom.ast.IBinding; |
| import org.eclipse.cdt.core.dom.ast.ICompositeType; |
| import org.eclipse.cdt.core.dom.ast.IEnumeration; |
| import org.eclipse.cdt.core.dom.ast.IFunction; |
| import org.eclipse.cdt.core.dom.ast.IFunctionType; |
| import org.eclipse.cdt.core.dom.ast.IMacroBinding; |
| import org.eclipse.cdt.core.dom.ast.IParameter; |
| import org.eclipse.cdt.core.dom.ast.IScope; |
| import org.eclipse.cdt.core.dom.ast.IType; |
| import org.eclipse.cdt.core.dom.ast.IVariable; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateDefinition; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateInstance; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateParameter; |
| import org.eclipse.cdt.core.index.IIndex; |
| import org.eclipse.cdt.core.index.IIndexFile; |
| import org.eclipse.cdt.core.index.IIndexFileLocation; |
| import org.eclipse.cdt.core.index.IIndexFileSet; |
| import org.eclipse.cdt.core.index.IIndexInclude; |
| import org.eclipse.cdt.core.index.IIndexName; |
| import org.eclipse.cdt.core.model.ITranslationUnit; |
| import org.eclipse.cdt.core.parser.util.ArrayUtil; |
| import org.eclipse.cdt.ui.CUIPlugin; |
| import org.eclipse.cdt.ui.CodeGeneration; |
| |
| import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.ASTCommenter; |
| 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.ILocationResolver; |
| import org.eclipse.cdt.internal.core.util.TextUtil; |
| import org.eclipse.cdt.internal.corext.codemanipulation.IncludeInfo; |
| import org.eclipse.cdt.internal.corext.codemanipulation.StyledInclude; |
| import org.eclipse.cdt.internal.formatter.ChangeFormatter; |
| |
| /** |
| * Organizes the include directives and forward declarations of a source or header file. |
| */ |
| public class IncludeOrganizer { |
| private static boolean DEBUG_HEADER_SUBSTITUTION = |
| Boolean.parseBoolean(Platform.getDebugOption(CUIPlugin.PLUGIN_ID + "/debug/includeOrganizer/headerSubstitution")); //$NON-NLS-1$ |
| |
| private static final Collator COLLATOR = Collator.getInstance(); |
| |
| /** |
| * Represents a new or an existing include statement. |
| */ |
| private static class IncludePrototype extends StyledInclude { |
| private final boolean required; // true if the header has to be included |
| |
| /** Initializes an include prototype object for a new include */ |
| IncludePrototype(IPath header, IncludeInfo includeInfo, IncludeGroupStyle style) { |
| super(header, includeInfo, style); |
| this.required = true; |
| } |
| |
| /** |
| * Initializes an include prototype object for an existing include. {@code header} may be |
| * {@code null} if the include was not resolved. |
| */ |
| IncludePrototype(IPath header, IncludeInfo includeInfo, IncludeGroupStyle style, |
| IASTPreprocessorIncludeStatement existingInclude, boolean required) { |
| super(header, includeInfo, style, existingInclude); |
| this.required = required; |
| } |
| |
| public boolean isRequired() { |
| return required; |
| } |
| } |
| |
| private static enum DeclarationType { TYPE, FUNCTION, VARIABLE, NAMESPACE } |
| |
| private static class ForwardDeclarationNode implements Comparable<ForwardDeclarationNode> { |
| final String name; |
| final String declaration; |
| final DeclarationType type; |
| final List<ForwardDeclarationNode> children; |
| |
| /** |
| * Creates a namespace node. |
| */ |
| ForwardDeclarationNode(String name) { |
| this.name = name; |
| this.declaration = null; |
| this.type = DeclarationType.NAMESPACE; |
| this.children = new ArrayList<>(); |
| } |
| |
| /** |
| * Creates a declaration node. |
| */ |
| ForwardDeclarationNode(String name, String declaration, DeclarationType type) { |
| this.name = name; |
| this.declaration = declaration; |
| this.type = type; |
| this.children = null; |
| } |
| |
| ForwardDeclarationNode findOrAddChild(ForwardDeclarationNode node) { |
| int i = Collections.binarySearch(children, node); |
| if (i >= 0) |
| return children.get(i); |
| children.add(-(i + 1), node); |
| return node; |
| } |
| |
| @Override |
| public int compareTo(ForwardDeclarationNode other) { |
| int c = type.ordinal() - other.type.ordinal(); |
| if (c != 0) |
| return c; |
| c = COLLATOR.compare(name, other.name); |
| if (declaration == null || c != 0) |
| return c; |
| return COLLATOR.compare(declaration, other.declaration); |
| } |
| } |
| |
| private final IHeaderChooser fHeaderChooser; |
| private final IncludeCreationContext fContext; |
| |
| public IncludeOrganizer(ITranslationUnit tu, IIndex index, IHeaderChooser headerChooser) { |
| fHeaderChooser = headerChooser; |
| fContext = new IncludeCreationContext(tu, index); |
| } |
| |
| /** |
| * Organizes the includes for a given translation unit. |
| * |
| * @param ast The AST translation unit to process. |
| */ |
| public MultiTextEdit organizeIncludes(IASTTranslationUnit ast) throws CoreException { |
| // Process the given translation unit with the inclusion resolver. |
| BindingClassifier bindingClassifier = new BindingClassifier(fContext); |
| bindingClassifier.classifyNodeContents(ast); |
| Set<IBinding> bindingsToInclude = bindingClassifier.getBindingsToDefine(); |
| |
| IASTPreprocessorIncludeStatement[] existingIncludes = ast.getIncludeDirectives(); |
| fContext.addHeadersIncludedPreviously(existingIncludes); |
| |
| HeaderSubstitutor headerSubstitutor = new HeaderSubstitutor(fContext); |
| // Create the list of header files which have to be included by examining the list of |
| // bindings which have to be defined. |
| IIndexFileSet reachableHeaders = ast.getIndexFileSet(); |
| |
| List<InclusionRequest> requests = createInclusionRequests(ast, bindingsToInclude, false, reachableHeaders); |
| processInclusionRequests(requests, existingIncludes, headerSubstitutor); |
| |
| NodeCommentMap commentedNodeMap = ASTCommenter.getCommentedNodeMap(ast); |
| |
| // Use a map instead of a set to be able to retrieve existing elements using equal elements. |
| // Maps each element to itself. |
| Map<IncludePrototype, IncludePrototype> includePrototypes = new HashMap<>(); |
| // Put the new includes into includePrototypes. |
| for (IPath header : fContext.getHeadersToInclude()) { |
| IncludeGroupStyle style = fContext.getIncludeStyle(header); |
| IncludeInfo includeInfo = fContext.createIncludeInfo(header, style); |
| IncludePrototype prototype = new IncludePrototype(header, includeInfo, style); |
| updateIncludePrototypes(includePrototypes, prototype); |
| } |
| // Add existing includes to includePrototypes. |
| for (IASTPreprocessorIncludeStatement include : existingIncludes) { |
| if (include.isPartOfTranslationUnitFile()) { |
| 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 ? fContext.getIncludeStyle(header) : fContext.getIncludeStyle(includeInfo); |
| boolean required = hasPragmaKeep(include, commentedNodeMap); |
| IncludePrototype prototype = new IncludePrototype(header, includeInfo, style, include, required); |
| updateIncludePrototypes(includePrototypes, prototype); |
| } |
| } |
| |
| IRegion includeReplacementRegion = |
| IncludeUtil.getSafeIncludeReplacementRegion(fContext.getSourceContents(), ast, commentedNodeMap); |
| |
| IncludePreferences preferences = fContext.getPreferences(); |
| boolean allowReordering = preferences.allowReordering || existingIncludes.length == 0; |
| |
| MultiTextEdit rootEdit = new MultiTextEdit(); |
| |
| @SuppressWarnings("unchecked") |
| List<IncludePrototype>[] groupedPrototypes = |
| (List<IncludePrototype>[]) new List<?>[preferences.includeStyles.size()]; |
| for (IncludePrototype prototype : includePrototypes.keySet()) { |
| if (prototype.getExistingInclude() == null |
| || (allowReordering && isContainedInRegion(prototype.getExistingInclude(), includeReplacementRegion))) { |
| IncludeGroupStyle groupingStyle = prototype.getStyle().getGroupingStyle(preferences.includeStyles); |
| // If reordering is not allowed, group everything together. |
| int position = allowReordering ? groupingStyle.getOrder() : 0; |
| List<IncludePrototype> prototypes = groupedPrototypes[position]; |
| if (prototypes == null) { |
| prototypes = new ArrayList<>(); |
| groupedPrototypes[position] = prototypes; |
| } |
| prototypes.add(prototype); |
| } |
| if (!allowReordering && prototype.getExistingInclude() != null |
| && !prototype.isRequired() && prototype.getHeader() != null // Unused and resolved. |
| && !fContext.isPartnerFile(prototype.getHeader()) |
| && isContainedInRegion(prototype.getExistingInclude(), includeReplacementRegion)) { |
| switch (preferences.unusedStatementsDisposition) { |
| case REMOVE: |
| createDelete(prototype.getExistingInclude(), rootEdit); |
| break; |
| case COMMENT_OUT: |
| createCommentOut(prototype.getExistingInclude(), rootEdit); |
| break; |
| case KEEP: |
| break; |
| } |
| } |
| } |
| |
| List<String> includeDirectives = new ArrayList<>(); |
| IncludeGroupStyle previousStyle = null; |
| for (List<IncludePrototype> prototypes : groupedPrototypes) { |
| if (prototypes != null && !prototypes.isEmpty()) { |
| Collections.sort(prototypes, preferences); |
| IncludeGroupStyle style = prototypes.get(0).getStyle(); |
| if (!includeDirectives.isEmpty() && |
| style.isBlankLineNeededAfter(previousStyle, preferences.includeStyles)) { |
| includeDirectives.add(""); // Blank line separator //$NON-NLS-1$ |
| } |
| previousStyle = style; |
| for (IncludePrototype prototype : prototypes) { |
| String trailingComment = ""; //$NON-NLS-1$ |
| IASTPreprocessorIncludeStatement include = prototype.getExistingInclude(); |
| if (include == null |
| || (allowReordering && IncludeUtil.isContainedInRegion(include, includeReplacementRegion))) { |
| if (include != null) { |
| List<IASTComment> comments = commentedNodeMap.getTrailingCommentsForNode(include); |
| StringBuilder buf = new StringBuilder(); |
| for (IASTComment comment : comments) { |
| buf.append(ASTNodes.getPrecedingWhitespaceInLine(fContext.getSourceContents(), comment)); |
| buf.append(comment.getRawSignature()); |
| } |
| trailingComment = buf.toString(); |
| } |
| String directive = createIncludeDirective(prototype, trailingComment); |
| if (directive != null) |
| includeDirectives.add(directive); |
| } |
| } |
| } |
| } |
| |
| // Create the source code to insert into the editor. |
| |
| StringBuilder buf = new StringBuilder(); |
| for (String include : includeDirectives) { |
| buf.append(include); |
| buf.append(fContext.getLineDelimiter()); |
| } |
| |
| int offset = includeReplacementRegion.getOffset(); |
| int length = includeReplacementRegion.getLength(); |
| if (allowReordering) { |
| if (buf.length() != 0) { |
| if (offset != 0 && !TextUtil.isPreviousLineBlank(fContext.getSourceContents(), offset)) |
| buf.insert(0, fContext.getLineDelimiter()); // Blank line before. |
| } |
| |
| String text = buf.toString(); |
| // TODO(sprigogin): Add a diff algorithm and produce narrower replacements. |
| if (text.length() != length || |
| !fContext.getSourceContents().regionMatches(offset, text, 0, length)) { |
| rootEdit.addChild(new ReplaceEdit(offset, length, text)); |
| } |
| } else if (buf.length() != 0) { |
| offset += length; |
| rootEdit.addChild(new InsertEdit(offset, buf.toString())); |
| } |
| |
| createForwardDeclarations(ast, bindingClassifier, |
| includeReplacementRegion.getOffset() + includeReplacementRegion.getLength(), |
| buf.length() != 0, rootEdit); |
| |
| return ChangeFormatter.formatChangedCode(new String(fContext.getSourceContents()), fContext.getTranslationUnit(), rootEdit); |
| } |
| |
| /** |
| * Creates forward declarations by examining the list of bindings which have to be declared. |
| */ |
| private void createForwardDeclarations(IASTTranslationUnit ast, BindingClassifier classifier, |
| int offset, boolean pendingBlankLine, MultiTextEdit rootEdit) throws CoreException { |
| ForwardDeclarationNode typeDeclarationsRoot = new ForwardDeclarationNode(""); //$NON-NLS-1$ |
| ForwardDeclarationNode nonTypeDeclarationsRoot = new ForwardDeclarationNode(""); //$NON-NLS-1$ |
| |
| IIndexFileSet reachableHeaders = ast.getIndexFileSet(); |
| Set<IBinding> bindings = |
| removeBindingsDefinedInIncludedHeaders(ast, classifier.getBindingsToForwardDeclare(), reachableHeaders); |
| for (IBinding binding : bindings) { |
| // Create the text of the forward declaration of this binding. |
| StringBuilder declarationText = new StringBuilder(); |
| |
| DeclarationType declarationType; |
| // Check the type of the binding and create a corresponding forward declaration text. |
| if (binding instanceof ICompositeType) { |
| declarationType = DeclarationType.TYPE; |
| // Forward declare a composite type. |
| ICompositeType compositeType = (ICompositeType) binding; |
| |
| // Check whether this is a template type. |
| ICPPTemplateDefinition templateDefinition = null; |
| if (compositeType instanceof ICPPTemplateDefinition) { |
| templateDefinition = (ICPPTemplateDefinition) compositeType; |
| } else if (compositeType instanceof ICPPTemplateInstance) { |
| templateDefinition = ((ICPPTemplateInstance) compositeType).getTemplateDefinition(); |
| } |
| if (templateDefinition != null) { |
| // Create the template text. |
| declarationText.append("template "); //$NON-NLS-1$ |
| ICPPTemplateParameter[] templateParameters = templateDefinition.getTemplateParameters(); |
| for (int i = 0; i < templateParameters.length; i++) { |
| ICPPTemplateParameter templateParameter = templateParameters[i]; |
| if (i == 0) { |
| declarationText.append("<"); //$NON-NLS-1$ |
| } |
| declarationText.append("typename "); //$NON-NLS-1$ |
| declarationText.append(templateParameter.getName()); |
| if (i != templateParameters.length - 1) { |
| declarationText.append(", "); //$NON-NLS-1$ |
| } |
| } |
| if (templateParameters.length > 0) { |
| declarationText.append("> "); //$NON-NLS-1$ |
| } |
| } |
| |
| // Append the corresponding keyword. |
| switch (compositeType.getKey()) { |
| case ICPPClassType.k_class: |
| declarationText.append("class"); //$NON-NLS-1$ |
| break; |
| case ICompositeType.k_struct: |
| declarationText.append("struct"); //$NON-NLS-1$ |
| break; |
| case ICompositeType.k_union: |
| declarationText.append("union"); //$NON-NLS-1$ |
| break; |
| } |
| |
| // Append the name of the composite type. |
| declarationText.append(' '); |
| declarationText.append(binding.getName()); |
| |
| // Append the semicolon. |
| declarationText.append(';'); |
| } else if (binding instanceof IEnumeration) { |
| declarationType = DeclarationType.TYPE; |
| // Forward declare an enumeration class (C++11 syntax). |
| declarationText.append("enum class "); //$NON-NLS-1$ |
| declarationText.append(binding.getName()); |
| declarationText.append(';'); |
| } else if (binding instanceof IFunction && !(binding instanceof ICPPMethod)) { |
| declarationType = DeclarationType.FUNCTION; |
| // Forward declare a C-style function. |
| IFunction function = (IFunction) binding; |
| |
| // Append return type and function name. |
| IFunctionType functionType = function.getType(); |
| // TODO(sprigogin): Switch to ASTWriter since ASTTypeUtil doesn't properly handle namespaces. |
| declarationText.append(ASTTypeUtil.getType(functionType.getReturnType(), false)); |
| declarationText.append(' '); |
| declarationText.append(function.getName()); |
| declarationText.append('('); |
| |
| // Append parameter types and names. |
| IType[] parameterTypes = functionType.getParameterTypes(); |
| IParameter[] parameters = function.getParameters(); |
| for (int i = 0; i < parameterTypes.length && i < parameters.length; i++) { |
| if (i != 0) { |
| declarationText.append(", "); //$NON-NLS-1$ |
| } |
| declarationText.append(ASTTypeUtil.getType(parameterTypes[i], false)); |
| char lastChar = declarationText.charAt(declarationText.length() - 1); |
| if (lastChar != '*' && lastChar != '&') { |
| // Append a space to separate the type name from the parameter name. |
| declarationText.append(' '); |
| } |
| declarationText.append(parameters[i].getName()); |
| } |
| |
| declarationText.append(");"); //$NON-NLS-1$ |
| } else if (binding instanceof IVariable) { |
| declarationType = DeclarationType.VARIABLE; |
| IVariable variable = (IVariable) binding; |
| IType variableType = variable.getType(); |
| declarationText.append("extern "); //$NON-NLS-1$ |
| declarationText.append(ASTTypeUtil.getType(variableType, false)); |
| declarationText.append(' '); |
| declarationText.append(variable.getName()); |
| declarationText.append(';'); |
| } else { |
| CUIPlugin.log(new IllegalArgumentException( |
| "Unexpected type of binding " + binding.getName() + //$NON-NLS-1$ |
| " - " + binding.getClass().getSimpleName())); //$NON-NLS-1$ |
| continue; |
| } |
| |
| // Consider the namespace(s) of the binding. |
| List<String> namespaces = new ArrayList<>(); |
| try { |
| IScope scope = binding.getScope(); |
| while (scope != null && scope.getKind() == EScopeKind.eNamespace) { |
| IName scopeName = scope.getScopeName(); |
| if (scopeName != null) { |
| namespaces.add(new String(scopeName.getSimpleID())); |
| } |
| scope = scope.getParent(); |
| } |
| } catch (DOMException e) { |
| } |
| |
| ForwardDeclarationNode parentNode = declarationType == DeclarationType.TYPE ? |
| typeDeclarationsRoot : nonTypeDeclarationsRoot; |
| |
| Collections.reverse(namespaces); |
| for (String ns : namespaces) { |
| ForwardDeclarationNode node = new ForwardDeclarationNode(ns); |
| parentNode = parentNode.findOrAddChild(node); |
| } |
| |
| ForwardDeclarationNode node = |
| new ForwardDeclarationNode(binding.getName(), declarationText.toString(), declarationType); |
| parentNode.findOrAddChild(node); |
| } |
| |
| StringBuilder buf = new StringBuilder(); |
| |
| for (ForwardDeclarationNode node : typeDeclarationsRoot.children) { |
| if (pendingBlankLine) { |
| buf.append(fContext.getLineDelimiter()); |
| pendingBlankLine = false; |
| } |
| printNode(node, buf); |
| } |
| |
| for (ForwardDeclarationNode node : nonTypeDeclarationsRoot.children) { |
| if (pendingBlankLine) { |
| buf.append(fContext.getLineDelimiter()); |
| pendingBlankLine = false; |
| } |
| printNode(node, buf); |
| } |
| |
| if ((pendingBlankLine || buf.length() != 0) && !isBlankLineOrEndOfFile(offset)) |
| buf.append(fContext.getLineDelimiter()); |
| |
| if (buf.length() != 0) |
| rootEdit.addChild(new InsertEdit(offset, buf.toString())); |
| } |
| |
| private void printNode(ForwardDeclarationNode node, StringBuilder buf) throws CoreException { |
| if (node.declaration == null) { |
| buf.append(CodeGeneration.getNamespaceBeginContent(fContext.getTranslationUnit(), node.name, fContext.getLineDelimiter())); |
| for (ForwardDeclarationNode child : node.children) { |
| printNode(child, buf); |
| } |
| buf.append(CodeGeneration.getNamespaceEndContent(fContext.getTranslationUnit(), node.name, fContext.getLineDelimiter())); |
| } else { |
| buf.append(node.declaration); |
| } |
| buf.append(fContext.getLineDelimiter()); |
| } |
| |
| private void createCommentOut(IASTPreprocessorIncludeStatement include, MultiTextEdit rootEdit) { |
| IASTFileLocation location = include.getFileLocation(); |
| int offset = location.getNodeOffset(); |
| if (fContext.getTranslationUnit().isCXXLanguage()) { |
| offset = TextUtil.getLineStart(fContext.getSourceContents(), offset); |
| rootEdit.addChild(new InsertEdit(offset, "//")); //$NON-NLS-1$ |
| } else { |
| rootEdit.addChild(new InsertEdit(offset, "/*")); //$NON-NLS-1$ |
| int endOffset = offset + location.getNodeLength(); |
| rootEdit.addChild(new InsertEdit(endOffset, "*/")); //$NON-NLS-1$ |
| } |
| } |
| |
| private void createDelete(IASTPreprocessorIncludeStatement include, MultiTextEdit rootEdit) { |
| IASTFileLocation location = include.getFileLocation(); |
| int offset = location.getNodeOffset(); |
| int endOffset = offset + location.getNodeLength(); |
| offset = TextUtil.getLineStart(fContext.getSourceContents(), offset); |
| endOffset = TextUtil.skipToNextLine(fContext.getSourceContents(), endOffset); |
| rootEdit.addChild(new DeleteEdit(offset, endOffset - offset)); |
| } |
| |
| private void updateIncludePrototypes(Map<IncludePrototype, IncludePrototype> includePrototypes, |
| IncludePrototype prototype) { |
| IncludePrototype existing = includePrototypes.get(prototype); |
| if (existing == null) { |
| includePrototypes.put(prototype, prototype); |
| } else { |
| existing.setExistingInclude(prototype.getExistingInclude()); |
| } |
| } |
| |
| /** |
| * Returns {@code true} if there are no non-whitespace characters between the given |
| * {@code offset} and the end of the line. |
| */ |
| private boolean isBlankLineOrEndOfFile(int offset) { |
| String contents = fContext.getSourceContents(); |
| while (offset < contents.length()) { |
| char c = contents.charAt(offset++); |
| if (c == '\n') |
| return true; |
| if (!Character.isWhitespace(c)) |
| return false; |
| } |
| return true; |
| } |
| |
| private Set<IBinding> removeBindingsDefinedInIncludedHeaders(IASTTranslationUnit ast, |
| Set<IBinding> bindings, IIndexFileSet reachableHeaders) throws CoreException { |
| List<InclusionRequest> requests = createInclusionRequests(ast, bindings, true, reachableHeaders); |
| Set<IPath> allIncludedHeaders = new HashSet<>(); |
| allIncludedHeaders.addAll(fContext.getHeadersAlreadyIncluded()); |
| allIncludedHeaders.addAll(fContext.getHeadersToInclude()); |
| |
| Set<IBinding> filteredBindings = new HashSet<>(bindings); |
| for (InclusionRequest request : requests) { |
| if (isSatisfiedByIncludedHeaders(request, allIncludedHeaders)) |
| filteredBindings.remove(request.getBinding()); |
| } |
| return filteredBindings; |
| } |
| |
| protected boolean isSatisfiedByIncludedHeaders(InclusionRequest request, Set<IPath> includedHeaders) |
| throws CoreException { |
| for (IIndexFile file : request.getDeclaringFiles().keySet()) { |
| IPath path = getAbsolutePath(file.getLocation()); |
| if (includedHeaders.contains(path)) |
| return true; |
| |
| IIndexInclude[] includedBy = fContext.getIndex().findIncludedBy(file, IIndex.DEPTH_INFINITE); |
| for (IIndexInclude include : includedBy) { |
| path = getAbsolutePath(include.getIncludedByLocation()); |
| if (includedHeaders.contains(path)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void processInclusionRequests(List<InclusionRequest> requests, |
| IASTPreprocessorIncludeStatement[] existingIncludes, HeaderSubstitutor headerSubstitutor) |
| throws CoreException { |
| // Add partner header if necessary. |
| IIndexFile partnerHeader = null; |
| for (InclusionRequest request : requests) { |
| List<IPath> candidatePaths = request.getCandidatePaths(); |
| if (candidatePaths.size() == 1) { |
| IPath path = candidatePaths.iterator().next(); |
| if (fContext.isPartnerFile(path)) { |
| request.resolve(path); |
| fContext.addHeaderToInclude(path); |
| partnerHeader = request.getDeclaringFiles().keySet().iterator().next(); |
| break; |
| } |
| } |
| } |
| |
| if (fContext.getPreferences().allowPartnerIndirectInclusion) { |
| // Mark all headers included by the partner header as already included. |
| if (partnerHeader == null) { |
| for (IASTPreprocessorIncludeStatement include : existingIncludes) { |
| if (include.isPartOfTranslationUnitFile()) { |
| IIndexFile header = include.getImportedIndexFile(); |
| if (header != null) { |
| if (fContext.isPartnerFile(new Path(include.getPath()))) { |
| partnerHeader = header; |
| break; |
| } |
| } |
| } |
| } |
| } |
| if (partnerHeader != null) { |
| for (IIndexInclude include : partnerHeader.getIncludes()) { |
| IIndexFileLocation headerLocation = include.getIncludesLocation(); |
| if (headerLocation != null) { |
| fContext.addHeaderAlreadyIncluded(getAbsolutePath(headerLocation)); |
| } |
| } |
| } |
| } |
| |
| // Process headers that are either indirectly included or have unique representatives. |
| for (InclusionRequest request : requests) { |
| if (!request.isResolved() && !isExportedBinding(request, headerSubstitutor)) { |
| List<IPath> candidatePaths = request.getCandidatePaths(); |
| Set<IPath> representativeHeaders = new HashSet<>(); |
| Set<IPath> representedHeaders = new HashSet<>(); |
| boolean allRepresented = true; |
| for (IPath path : candidatePaths) { |
| if (fContext.isIncluded(path)) { |
| request.resolve(path); |
| if (DEBUG_HEADER_SUBSTITUTION) { |
| System.out.println(request.toString() + |
| (fContext.isToBeIncluded(path) ? " (decided earlier)" : " (was previously included)")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| break; |
| } else { |
| IPath header = headerSubstitutor.getUniqueRepresentativeHeader(path); |
| if (header != null) { |
| representativeHeaders.add(header); |
| representedHeaders.add(path); |
| } else { |
| allRepresented = false; |
| } |
| } |
| } |
| |
| if (!request.isResolved() && allRepresented && representativeHeaders.size() == 1) { |
| IPath path = representativeHeaders.iterator().next(); |
| request.resolve(path); |
| if (DEBUG_HEADER_SUBSTITUTION) |
| System.out.println(request.toString() + " (unique representative)"); //$NON-NLS-1$ |
| if (!fContext.isAlreadyIncluded(path)) |
| fContext.addHeaderToInclude(path); |
| for (IPath header : representedHeaders) { |
| if (!header.equals(path)) |
| fContext.addHeaderAlreadyIncluded(header); |
| } |
| } |
| } |
| } |
| |
| // Process remaining unambiguous inclusion requests. |
| for (InclusionRequest request : requests) { |
| if (!request.isResolved() && !isExportedBinding(request, headerSubstitutor)) { |
| List<IPath> candidatePaths = request.getCandidatePaths(); |
| if (candidatePaths.size() == 1) { |
| IPath path = candidatePaths.iterator().next(); |
| if (fContext.isIncluded(path)) { |
| request.resolve(path); |
| if (DEBUG_HEADER_SUBSTITUTION) { |
| System.out.println(request.toString() + |
| (fContext.isToBeIncluded(path) ? " (decided earlier)" : " (was previously included)")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } else { |
| IPath header = headerSubstitutor.getPreferredRepresentativeHeader(path); |
| if (header.equals(path) && fContext.getPreferences().heuristicHeaderSubstitution) { |
| header = headerSubstitutor.getPreferredRepresentativeHeaderByHeuristic(request); |
| } |
| request.resolve(header); |
| if (DEBUG_HEADER_SUBSTITUTION) { |
| System.out.println(request.toString() + " (preferred representative)"); //$NON-NLS-1$ |
| } |
| if (!fContext.isAlreadyIncluded(header)) |
| fContext.addHeaderToInclude(header); |
| if (!header.equals(path)) |
| fContext.addHeaderAlreadyIncluded(path); |
| } |
| } |
| } |
| } |
| |
| // Resolve ambiguous inclusion requests. |
| for (InclusionRequest request : requests) { |
| if (!request.isResolved() && !isExportedBinding(request, headerSubstitutor)) { |
| List<IPath> candidatePaths = request.getCandidatePaths(); |
| for (IPath path : candidatePaths) { |
| if (fContext.isIncluded(path)) { |
| request.resolve(path); |
| if (DEBUG_HEADER_SUBSTITUTION) { |
| System.out.println(request.toString() + |
| (fContext.isToBeIncluded(path) ? " (decided earlier)" : " (was previously included)")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| break; |
| } |
| } |
| if (!request.isResolved()) { |
| IPath header = fHeaderChooser.chooseHeader(request.getBinding().getName(), candidatePaths); |
| if (header == null) |
| throw new OperationCanceledException(); |
| |
| request.resolve(header); |
| if (DEBUG_HEADER_SUBSTITUTION) { |
| System.out.println(request.toString() + " (user's choice)"); //$NON-NLS-1$ |
| } |
| if (!fContext.isAlreadyIncluded(header)) |
| fContext.addHeaderToInclude(header); |
| } |
| } |
| } |
| |
| // Resolve requests for exported symbols. |
| for (InclusionRequest request : requests) { |
| if (!request.isResolved()) { |
| IPath firstIncludedPreviously = null; |
| Set<IncludeInfo> exportingHeaders = getExportingHeaders(request, headerSubstitutor); |
| for (IncludeInfo header : exportingHeaders) { |
| IPath path = fContext.resolveInclude(header); |
| if (path != null) { |
| if (fContext.isIncluded(path)) { |
| request.resolve(path); |
| if (DEBUG_HEADER_SUBSTITUTION) { |
| System.out.println(request.toString() + |
| (fContext.isToBeIncluded(path) ? " (decided earlier)" : " (was previously included)")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| break; |
| } |
| if (firstIncludedPreviously == null && fContext.wasIncludedPreviously(path)) |
| firstIncludedPreviously = path; |
| } |
| } |
| if (request.isResolved()) |
| continue; |
| |
| List<IPath> candidatePaths = request.getCandidatePaths(); |
| for (IPath path : candidatePaths) { |
| if (fContext.isIncluded(path)) { |
| request.resolve(path); |
| if (DEBUG_HEADER_SUBSTITUTION) { |
| System.out.println(request.toString() + |
| (fContext.isToBeIncluded(path) ? " (decided earlier)" : " (was previously included)")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| break; |
| } |
| if (firstIncludedPreviously == null && fContext.wasIncludedPreviously(path)) |
| firstIncludedPreviously = path; |
| } |
| |
| if (request.isResolved()) |
| continue; |
| |
| if (firstIncludedPreviously != null) { |
| request.resolve(firstIncludedPreviously); |
| if (DEBUG_HEADER_SUBSTITUTION) { |
| System.out.println(request.toString() + " (present in old includes)"); //$NON-NLS-1$ |
| } |
| if (!fContext.isAlreadyIncluded(firstIncludedPreviously)) |
| fContext.addHeaderToInclude(firstIncludedPreviously); |
| } |
| |
| if (!request.isResolved()) { |
| IPath header = fHeaderChooser.chooseHeader(request.getBinding().getName(), candidatePaths); |
| if (header == null) |
| throw new OperationCanceledException(); |
| |
| request.resolve(header); |
| if (DEBUG_HEADER_SUBSTITUTION) { |
| System.out.println(request.toString() + |
| (candidatePaths.size() == 1 ? " (the only choice)" : " (user's choice)")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| if (!fContext.isAlreadyIncluded(header)) |
| fContext.addHeaderToInclude(header); |
| } |
| } |
| } |
| |
| // Remove headers that are exported by other headers. |
| fContext.removeExportedHeaders(); |
| } |
| |
| private boolean isExportedBinding(InclusionRequest request, HeaderSubstitutor headerSubstitutor) { |
| return !getExportingHeaders(request, headerSubstitutor).isEmpty(); |
| } |
| |
| private Set<IncludeInfo> getExportingHeaders(InclusionRequest request, HeaderSubstitutor headerSubstitutor) { |
| String symbol = request.getBindingQualifiedName(); |
| if (symbol == null) |
| return Collections.emptySet(); |
| return headerSubstitutor.getExportingHeaders(symbol); |
| } |
| |
| private List<InclusionRequest> createInclusionRequests(IASTTranslationUnit ast, |
| Set<IBinding> bindingsToInclude, boolean allowDeclarations, |
| IIndexFileSet reachableHeaders) throws CoreException { |
| List<InclusionRequest> requests = new ArrayList<>(bindingsToInclude.size()); |
| IIndex index = fContext.getIndex(); |
| |
| binding_loop: for (IBinding binding : bindingsToInclude) { |
| IIndexName[] indexNames; |
| if (binding instanceof IMacroBinding) { |
| indexNames = IIndexName.EMPTY_ARRAY; |
| ILocationResolver resolver = ast.getAdapter(ILocationResolver.class); |
| IASTName[] declarations = resolver.getDeclarations((IMacroBinding) binding); |
| for (IASTName name : declarations) { |
| if (name instanceof IAdaptable) { |
| IIndexName indexName = ((IAdaptable) name).getAdapter(IIndexName.class); |
| if (indexName != null) { |
| indexNames = Arrays.copyOf(indexNames, indexNames.length + 1); |
| indexNames[indexNames.length - 1] = indexName; |
| } |
| } |
| } |
| } else if (allowDeclarations || binding instanceof IVariable) { |
| // For a variable we need to include a declaration. |
| indexNames = index.findDeclarations(binding); |
| } else if (binding instanceof ICPPMethod) { |
| // Include the headers containing method definitions except the ones also containing |
| // the definition of the owner class. The definition of owner class will be included because |
| // BindingClassifier must add a binding that is either the owner itself, or its subclass, or |
| // a typedef pointing to it. |
| Set<IIndexFile> declarationFiles = new HashSet<>(); |
| IIndexName[] declarations = index.findNames(binding, IIndex.FIND_DECLARATIONS); |
| for (IIndexName declaration : declarations) { |
| IIndexFile file = declaration.getFile(); |
| if (file != null) { |
| declarationFiles.add(file); |
| } |
| } |
| IIndexName[] definitions = index.findDefinitions(binding); |
| indexNames = filterIncludableNotInBlacklistedFiles(definitions, declarationFiles); |
| } else { |
| indexNames = index.findDefinitions(binding); |
| if (binding instanceof IFunction) { |
| // If a function is defined in a header, include that header. |
| // Otherwise look for declarations. |
| indexNames = filterIncludableNotInBlacklistedFiles(indexNames, Collections.<IIndexFile>emptySet()); |
| } |
| if (indexNames.length == 0) { |
| // If we could not find any definitions, there is still a chance that |
| // a declaration would be sufficient. |
| indexNames = index.findDeclarations(binding); |
| } |
| } |
| |
| if (indexNames.length != 0) { |
| // Check whether the index name is (also) present within the current file. |
| // If yes, we don't need to include anything. |
| for (IIndexName indexName : indexNames) { |
| IIndexFile indexFile = indexName.getFile(); |
| if (indexFile.getLocation().getURI().equals(fContext.getTranslationUnit().getLocationURI())) { |
| continue binding_loop; |
| } |
| } |
| |
| Map<IIndexFile, IPath> declaringHeaders = new HashMap<>(); |
| Map<IIndexFile, IPath> reachableDeclaringHeaders = new HashMap<>(); |
| for (IIndexName indexName : indexNames) { |
| IIndexFile indexFile = indexName.getFile(); |
| if (!fContext.canBeIncluded(indexFile)) { |
| // The target is a source file which isn't included by any other files. |
| // Don't include it. |
| continue; |
| } |
| IPath path = getAbsolutePath(indexFile.getLocation()); |
| declaringHeaders.put(indexFile, path); |
| if (reachableHeaders.contains(indexFile)) |
| reachableDeclaringHeaders.put(indexFile, path); |
| } |
| |
| if (!declaringHeaders.isEmpty()) { |
| boolean reachable = false; |
| if (!reachableDeclaringHeaders.isEmpty()) { |
| reachable = true; |
| declaringHeaders = reachableDeclaringHeaders; |
| } |
| requests.add(new InclusionRequest(binding, declaringHeaders, reachable)); |
| } |
| } |
| } |
| return requests; |
| } |
| |
| private IIndexName[] filterIncludableNotInBlacklistedFiles(IIndexName[] names, Set<IIndexFile> blacklist) |
| throws CoreException { |
| IIndexName[] includable = IIndexName.EMPTY_ARRAY; |
| int pos = 0; |
| for (IIndexName name : names) { |
| IIndexFile file = name.getFile(); |
| if (file != null && !blacklist.contains(file) && fContext.canBeIncluded(file)) |
| includable = ArrayUtil.appendAt(includable, pos++, name); |
| } |
| return ArrayUtil.trim(includable, pos); |
| } |
| |
| private String createIncludeDirective(IncludePrototype include, String lineComment) { |
| StringBuilder buf = new StringBuilder(); |
| // Unresolved includes are preserved out of caution. Partner include is always preserved. |
| if (!include.isRequired() && include.getHeader() != null |
| && !fContext.isPartnerFile(include.getHeader())) { |
| switch (fContext.getPreferences().unusedStatementsDisposition) { |
| case REMOVE: |
| return null; |
| case COMMENT_OUT: |
| buf.append("//"); //$NON-NLS-1$ |
| break; |
| case KEEP: |
| break; |
| } |
| } |
| buf.append(include.getIncludeInfo().composeIncludeStatement()); |
| buf.append(lineComment); |
| return buf.toString(); |
| } |
| |
| private boolean hasPragmaKeep(IASTPreprocessorIncludeStatement include, NodeCommentMap commentedNodeMap) { |
| List<IASTComment> comments = commentedNodeMap.getTrailingCommentsForNode(include); |
| for (IASTComment comment : comments) { |
| String text = getTrimmedCommentText(comment); |
| if (fContext.getKeepPragmaPattern().matcher(text).matches()) |
| return true; |
| } |
| return false; |
| } |
| |
| private String getTrimmedCommentText(IASTComment comment) { |
| char[] text = comment.getComment(); |
| int end = text.length - (comment.isBlockComment() ? 2 : 0); |
| int begin; |
| for (begin = 2; begin < end; begin++) { |
| if (!Character.isWhitespace(text[begin])) |
| break; |
| } |
| if (end <= begin) |
| return ""; //$NON-NLS-1$ |
| while (--end >= begin) { |
| if (!Character.isWhitespace(text[end])) |
| break; |
| } |
| return new String(text, begin, end + 1 - begin); |
| } |
| } |