/*******************************************************************************
* Copyright (c) 2008, 2016 Institute for Software, HSR Hochschule fuer Technik
* Rapperswil, University of applied sciences 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:
* Institute for Software - initial API and implementation
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.refactoring;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFieldReference;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTMacroExpansionLocation;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTNodeLocation;
import org.eclipse.cdt.core.dom.ast.IASTPointerOperator;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IVariable;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTReferenceOperator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateTypeParameter;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.PreferenceConstants;
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVariableReadWriteFlags;
import org.eclipse.cdt.internal.core.dom.rewrite.util.ASTNodes;
import org.eclipse.cdt.internal.core.pdom.dom.PDOMName;
import org.eclipse.cdt.internal.corext.refactoring.code.flow.FlowContext;
import org.eclipse.cdt.internal.corext.refactoring.code.flow.FlowInfo;
import org.eclipse.cdt.internal.corext.refactoring.code.flow.InputFlowAnalyzer;
import org.eclipse.cdt.internal.corext.refactoring.code.flow.Selection;
public class NodeContainer {
private final List<IASTNode> nodes;
private List<NameInformation> names;
private List<NameInformation> interfaceNames;
public NodeContainer() {
super();
nodes = new ArrayList<IASTNode>();
}
public final int size() {
return nodes.size();
}
public final boolean isEmpty() {
return nodes.isEmpty();
}
public void add(IASTNode node) {
nodes.add(node);
}
private void findAllNames() {
if (names != null) {
return;
}
names = new ArrayList<NameInformation>();
final int startOffset = getStartOffset();
final int endOffset = getEndOffset();
IPreferencesService preferences = Platform.getPreferencesService();
final boolean passOutputByPointer = preferences.getBoolean(CUIPlugin.PLUGIN_ID,
PreferenceConstants.FUNCTION_PASS_OUTPUT_PARAMETERS_BY_POINTER, false,
PreferenceConstants.getPreferenceScopes(getProject()));
for (IASTNode node : nodes) {
node.accept(new ASTVisitor() {
{
shouldVisitNames = true;
}
@Override
public int visit(IASTName name) {
if (name.getPropertyInParent() != IASTFieldReference.FIELD_NAME) {
IBinding binding = name.resolveBinding();
if (binding instanceof ICPPBinding && !(binding instanceof ICPPTemplateTypeParameter)) {
ICPPBinding cppBinding = (ICPPBinding) binding;
try {
if (!cppBinding.isGloballyQualified()) {
NameInformation nameInfo = new NameInformation(name);
nameInfo.setPassOutputByPointer(passOutputByPointer);
IASTName[] refs = name.getTranslationUnit().getReferences(binding);
for (IASTName ref : refs) {
nameInfo.addReference(ref, startOffset, endOffset);
}
names.add(nameInfo);
}
} catch (DOMException e) {
IStatus status = new Status(IStatus.WARNING, CUIPlugin.PLUGIN_ID,
e.getMessage(), e);
CUIPlugin.log(status);
}
} else if (binding instanceof IVariable) {
NameInformation nameInformation = new NameInformation(name);
IASTName[] refs = name.getTranslationUnit().getReferences(binding);
for (IASTName ref : refs) {
nameInformation.addReference(ref, startOffset, endOffset);
}
names.add(nameInformation);
}
}
return super.visit(name);
}
});
}
for (NameInformation nameInfo : names) {
IASTName name = nameInfo.getName();
IASTTranslationUnit ast = name.getTranslationUnit();
IASTName[] nameDeclarations = ast.getDeclarationsInAST(name.resolveBinding());
if (nameDeclarations.length != 0) {
nameInfo.setDeclarationName(nameDeclarations[nameDeclarations.length - 1]);
}
}
}
private IProject getProject() {
IProject project = null;
if (nodes.isEmpty()) {
ITranslationUnit tu = nodes.get(0).getTranslationUnit().getOriginatingTranslationUnit();
if (tu != null)
project = tu.getCProject().getProject();
}
return project;
}
/**
* Returns names that are either parameter or return value candidates.
*/
private List<NameInformation> getInterfaceNames() {
if (interfaceNames == null) {
findAllNames();
Set<IVariable> externalReads = getVariablesReadOutside();
Set<IASTName> declarations = new HashSet<IASTName>();
interfaceNames = new ArrayList<NameInformation>();
for (NameInformation nameInfo : names) {
IASTName declarationName = nameInfo.getDeclarationName();
if (declarations.add(declarationName)) {
if (isDeclaredInSelection(nameInfo)) {
if (externalReads.contains(nameInfo.getName().resolveBinding())) {
nameInfo.setMustBeReturnValue(true);
interfaceNames.add(nameInfo);
}
} else {
IASTDeclarator declarator = (IASTDeclarator) declarationName.getParent();
if (!hasReferenceOperator(declarator)) {
for (NameInformation n2 : names) {
if (n2.getDeclarationName() == declarationName) {
int flag = CPPVariableReadWriteFlags.getReadWriteFlags(n2.getName());
if ((flag & PDOMName.WRITE_ACCESS) != 0) {
nameInfo.setWriteAccess(true);
break;
}
}
}
if (nameInfo.isWriteAccess() &&
externalReads.contains(nameInfo.getName().resolveBinding())) {
nameInfo.setOutput(true);
}
}
interfaceNames.add(nameInfo);
}
}
}
}
return interfaceNames;
}
private Set<IVariable> getVariablesReadOutside() {
if (nodes.isEmpty())
return Collections.emptySet();
IASTNode firstNode = nodes.get(0);
IASTFunctionDefinition enclosingFunction =
ASTQueries.findAncestorWithType(firstNode, IASTFunctionDefinition.class);
FlowContext flowContext= new FlowContext(enclosingFunction);
flowContext.setConsiderAccessMode(true);
flowContext.setComputeMode(FlowContext.ARGUMENTS);
// Compute a selection that exactly covers the selected nodes
Selection selection= Selection.createFromStartEnd(ASTNodes.offset(firstNode),
ASTNodes.endOffset(nodes.get(nodes.size() - 1)));
InputFlowAnalyzer analyzer = new InputFlowAnalyzer(flowContext, selection, true);
FlowInfo argInfo= analyzer.perform(enclosingFunction);
Set<IVariable> variables = argInfo.get(flowContext, FlowInfo.READ | FlowInfo.READ_POTENTIAL | FlowInfo.UNKNOWN);
// Remove variables with scopes limited to the selection.
for (Iterator<IVariable> iter = variables.iterator(); iter.hasNext();) {
IVariable var = iter.next();
try {
IASTNode scopeNode = ASTInternal.getPhysicalNodeOfScope(var.getScope());
if (selection.covers(scopeNode))
iter.remove();
} catch (DOMException e) {
}
}
return variables;
}
public static boolean hasReferenceOperator(IASTDeclarator declarator) {
IASTPointerOperator[] operators = declarator.getPointerOperators();
return operators.length != 0 && operators[operators.length - 1] instanceof ICPPASTReferenceOperator;
}
public boolean isDeclaredInSelection(NameInformation nameInfo) {
IASTName declaration = nameInfo.getDeclarationName();
if (declaration != null && declaration.toCharArray().length > 0) {
int declOffset = declaration.getFileLocation().getNodeOffset();
return declOffset >= getStartOffset() && declOffset <= getEndOffset();
}
return true;
}
private List<NameInformation> getInterfaceNames(boolean isReturnValue) {
List<NameInformation> selectedNames = null;
for (NameInformation nameInfo : getInterfaceNames()) {
if (nameInfo.mustBeReturnValue() == isReturnValue) {
if (selectedNames == null) {
selectedNames = new ArrayList<NameInformation>();
}
selectedNames.add(nameInfo);
}
}
if (selectedNames == null) {
selectedNames = Collections.emptyList();
}
return selectedNames;
}
/**
* Returns names that are candidates to be used as function parameters.
*/
public List<NameInformation> getParameterCandidates() {
return getInterfaceNames(false);
}
/**
* Returns names that are candidates for being used as the function return value. Multiple
* return value candidates mean that the function cannot be extracted.
*/
public List<NameInformation> getReturnValueCandidates() {
return getInterfaceNames(true);
}
public List<IASTNode> getNodesToWrite() {
return nodes;
}
public int getStartOffset() {
return getOffset(false);
}
public int getStartOffsetIncludingComments() {
return getOffset(true);
}
private int getOffset(boolean includeComments) {
int start = Integer.MAX_VALUE;
for (IASTNode node : nodes) {
int nodeStart = Integer.MAX_VALUE;
IASTNodeLocation[] nodeLocations = node.getNodeLocations();
if (nodeLocations.length != 1) {
for (IASTNodeLocation location : nodeLocations) {
int nodeOffset;
if (location instanceof IASTMacroExpansionLocation) {
IASTMacroExpansionLocation macroLoc = (IASTMacroExpansionLocation) location;
nodeOffset = macroLoc.asFileLocation().getNodeOffset();
} else {
nodeOffset = node.getFileLocation().getNodeOffset();
}
if (nodeOffset < nodeStart) {
nodeStart = nodeOffset;
}
}
} else {
nodeStart = node.getFileLocation().getNodeOffset();
}
if (nodeStart < start) {
start = nodeStart;
}
}
return start;
}
public int getEndOffset() {
return getEndOffset(false);
}
public int getEndOffsetIncludingComments() {
return getEndOffset(true);
}
private int getEndOffset(boolean includeComments) {
int end = 0;
for (IASTNode node : nodes) {
int fileOffset = 0;
int length = 0;
IASTNodeLocation[] nodeLocations = node.getNodeLocations();
for (IASTNodeLocation location : nodeLocations) {
int nodeOffset, nodeLength;
if (location instanceof IASTMacroExpansionLocation) {
IASTMacroExpansionLocation macroLoc = (IASTMacroExpansionLocation) location;
nodeOffset = macroLoc.asFileLocation().getNodeOffset();
nodeLength = macroLoc.asFileLocation().getNodeLength();
} else {
nodeOffset = location.getNodeOffset();
nodeLength = location.getNodeLength();
}
if (fileOffset < nodeOffset) {
fileOffset = nodeOffset;
length = nodeLength;
}
}
int endNode = fileOffset + length;
if (endNode > end) {
end = endNode;
}
}
return end;
}
@Override
public String toString() {
return nodes.toString();
}
public List<NameInformation> getNames() {
findAllNames();
return names;
}
}