blob: 92cba70ee2cfbd29afa6b3259dff0ae576ec4d2a [file] [log] [blame]
/*******************************************************************************
* 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);
}
}