/*******************************************************************************
* Copyright (c) 2008, 2013 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.core.dom.rewrite.commenthandler;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
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.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateDeclaration;
import org.eclipse.cdt.internal.core.dom.parser.ASTNode;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTDeclarationStatement;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTExplicitTemplateInstantiation;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTForStatement;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTFunctionDefinition;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTIfStatement;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTLabelStatement;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTLinkageSpecification;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTSwitchStatement;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTemplateDeclaration;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTWhileStatement;
/**
* The NodeCommenter contains all the logic that is needed for the ASTCommentVisitor to assign
* the comments to the suitable node. Together with the ASTCommenterVisitor it fills all
* the comments with the correspondent node into the NodeCommentMap.
*
* Following, a little explanation of the assignment logic. It is only a loose illustration
* a detailed description would include a combined explanation of ASTCommenterVisitor and
* NodeCommenter.<br>
* To understand the logic we define the three types of comments:<br>
* leading comments - Comments before a statement, declaration, or definition.<br>
* trailing comments - Comments right after the AST node on the same line.<br>
* freestanding comments - Comments before a closing brace such as they occur in
* namespace-, class- and method-definitions or at the end of a file.<br>
*
* The first comment is fetched and the position of it is compared to the position of the actual
* node. If the position of the comment is smaller than the comment is added to the node as leading.
* If it is behind the node but on the same line it is added as trailing. If one of these
* possibilities match the next comment is fetched for the same check. If it doesn't match the same
* procedure is done for all the child nodes. After checking the sub nodes the actual node is
* checked again if the comment is trailing. Then there is also the possibility that this comment is
* freestanding. This is the case when the comment is not added to any child node but the position
* is smaller than the end position of the node.
*
* @author Guido Zgraggen IFS
*/
public class NodeCommenter {
protected ASTVisitor visitor;
protected CommentHandler commentHandler;
protected NodeCommentMap commentMap;
protected List<IASTNode> children;
public NodeCommenter(ASTVisitor visitor, CommentHandler commHandler, NodeCommentMap commentMap) {
this.visitor = visitor;
this.commentHandler = commHandler;
this.commentMap = commentMap;
this.children = new ArrayList<IASTNode>();
}
protected void writeNodeList(IASTNode[] nodes) {
for (int i = 0; i < nodes.length; ++i) {
nodes[i].accept(visitor);
}
}
protected void visitNodeIfNotNull(IASTNode node){
if (node != null){
node.accept(visitor);
}
}
protected boolean appendComment(ASTNode node, IASTComment comment) {
ASTNode com = (ASTNode) comment;
if (node.getFileLocation() == null) {
// MacroExpansions have no FileLocation
return false;
}
int nodeLineNumber = getEndingLineNumber(node);
int commentLineNumber= getStartingLineNumber(comment);
if (getNodeEndPoint(com) <= getNodeOffset(node)) {
addLeadingCommentToMap(node, comment);
return true;
} else if (isTrailing(node, com, nodeLineNumber, commentLineNumber)) {
addTrailingCommentToMap(node, comment);
return true;
}
return false;
}
protected boolean appendFreestandingComment(ASTNode node, IASTComment comment) {
ASTNode com = (ASTNode) comment;
if (node.getFileLocation() == null) {
// MacroExpansions have no FileLocation
return false;
}
if (getNodeEndPoint(com) <= getNodeEndPoint(node)) {
addFreestandingCommentToMap(node, comment);
return true;
}
return false;
}
private void addLeadingCommentToMap(ASTNode node, IASTComment comment) {
commentMap.addLeadingCommentToNode(node, comment);
commentHandler.allreadyAdded(comment);
}
private void addTrailingCommentToMap(ASTNode node, IASTComment comment) {
commentMap.addTrailingCommentToNode(node, comment);
commentHandler.allreadyAdded(comment);
}
private void addFreestandingCommentToMap(ASTNode node, IASTComment comment) {
commentMap.addFreestandingCommentToNode(node, comment);
commentHandler.allreadyAdded(comment);
}
private boolean isTrailing(ASTNode node, ASTNode com, int nodeLineNumber, int commentLineNumber) {
if (nodeLineNumber != commentLineNumber ||
getNodeOffset(com) < getNodeEndPoint(node) ||
!canNotBeAddedToParent(node, com) ||
mustBeAddedToSubnodes(node)) {
return false;
}
if (getNodeOffset(com) < getNodeEndPoint(node) + 2) {
return true;
}
String code = node.getTranslationUnit().getRawSignature();
int commentOffset = getNodeOffset(com) - getNodeEndPoint(node) + getNodeEndOffset(node);
for (int offset = getNodeEndOffset(node); offset < commentOffset; offset++) {
if (!Character.isWhitespace(code.charAt(offset)))
return false;
}
return true;
}
private boolean canNotBeAddedToParent(ASTNode node, ASTNode com) {
ASTNode parent = (ASTNode) node.getParent();
if (hasNodeSameEndingAsSubnode(parent)) {
return true;
} else if (parent instanceof IASTTranslationUnit) {
return true;
} else if (parent instanceof ICPPASTTemplateDeclaration) {
return true;
} else if (parent instanceof CPPASTIfStatement) {
return true;
} else if (parent instanceof ICPPASTBaseSpecifier) {
parent = (ASTNode) parent.getParent();
}
return getNodeOffset(com) < getNodeEndPoint(parent);
}
private boolean mustBeAddedToSubnodes(ASTNode node) {
return hasNodeSameEndingAsSubnode(node);
}
private boolean hasNodeSameEndingAsSubnode(ASTNode node) {
if (node instanceof CPPASTFunctionDefinition) {
return true;
} else if (node instanceof CPPASTDeclarationStatement) {
return true;
} else if (node instanceof CPPASTForStatement) {
return true;
} else if (node instanceof CPPASTLabelStatement) {
return true;
} else if (node instanceof CPPASTIfStatement) {
return true;
} else if (node instanceof CPPASTSwitchStatement) {
return true;
} else if (node instanceof CPPASTWhileStatement) {
return true;
} else if (node instanceof CPPASTTemplateDeclaration) {
return true;
} else if (node instanceof CPPASTLinkageSpecification) {
return true;
} else if (node instanceof CPPASTExplicitTemplateInstantiation) {
return true;
}
return false;
}
protected int appendComments(ASTNode node) {
while (commentHandler.hasMore()) {
IASTComment comment = commentHandler.getFirst();
if (isNotSameFile(node, comment)) {
return ASTVisitor.PROCESS_SKIP;
}
if (!appendComment(node, comment)) {
return ASTVisitor.PROCESS_CONTINUE;
}
}
return ASTVisitor.PROCESS_ABORT;
}
protected int appendFreestandingComments(ASTNode node) {
while (commentHandler.hasMore()) {
IASTComment comment = commentHandler.getFirst();
if (isNotSameFile(node, comment)) {
return ASTVisitor.PROCESS_SKIP;
}
if (appendComment(node, comment)) {
return ASTVisitor.PROCESS_CONTINUE;
}
if (!appendFreestandingComment(node, comment)) {
return ASTVisitor.PROCESS_CONTINUE;
}
}
return ASTVisitor.PROCESS_ABORT;
}
public void appendRemainingComments(IASTDeclaration declaration) {
while (commentHandler.hasMore()) {
IASTComment comment = commentHandler.getFirst();
if (appendComment((ASTNode) declaration, comment)) {
continue;
}
addFreestandingCommentToMap((ASTNode) declaration, comment);
}
}
private boolean isNotSameFile(IASTNode node, IASTComment comment) {
if (node.getFileLocation() == null) {
return true;
}
return !node.getFileLocation().getFileName().equals(comment.getFileLocation().getFileName());
}
private static int getNodeEndOffset(IASTNode node) {
IASTFileLocation fileLocation = node.getFileLocation();
return fileLocation.getNodeOffset() + fileLocation.getNodeLength();
}
private static int getNodeOffset(ASTNode node) {
return node.getOffset();
}
private static int getNodeEndPoint(ASTNode node) {
return node.getOffset() + node.getLength();
}
private static int getStartingLineNumber(IASTNode node) {
return node.getFileLocation().getStartingLineNumber();
}
private static int getEndingLineNumber(IASTNode node) {
return node.getFileLocation().getEndingLineNumber();
}
}