/*******************************************************************************
* Copyright (c) 2008, 2014 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.Arrays;
import java.util.Iterator;
import java.util.List;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTArrayModifier;
import org.eclipse.cdt.core.dom.ast.IASTComment;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier.IASTEnumerator;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTInitializer;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTPointerOperator;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IASTTypeId;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateParameter;
/**
* This is the starting point of the entire comment handling process. The creation of the
* {@link NodeCommentMap} is based on the {@link IASTTranslationUnit}. From this translation unit
* the comments are extracted and skipped if they belong not to the same workspace.
* An {@link ASTCommenterVisitor} is initialized with this collection of comments. And the visit
* process can start.
*
* @see NodeCommenter
* @see NodeCommentMap
*
* @author Guido Zgraggen IFS
*/
public class ASTCommenter {
private static final class PreprocessorRangeChecker extends ASTVisitor {
int ppStmtOffset;
IASTFileLocation commentLocation;
boolean isPrePpStmtComment = true;
private PreprocessorRangeChecker(int statementOffset, IASTFileLocation commentLocation) {
super(true);
this.ppStmtOffset = statementOffset;
this.commentLocation = commentLocation;
}
private int checkOffsets(IASTNode node) {
IASTFileLocation nodeLocation = node.getFileLocation();
if (nodeLocation == null)
return PROCESS_SKIP;
int nodeEndOffset = nodeLocation.getNodeOffset() + nodeLocation.getNodeLength();
boolean nodeInBetweenCommentAndPpStmt =
nodeEndOffset > commentLocation.getNodeOffset() && nodeEndOffset < ppStmtOffset;
if (isCommentOnSameLine(node) || nodeInBetweenCommentAndPpStmt) {
isPrePpStmtComment = false;
return PROCESS_ABORT;
} else if (nodeEndOffset < commentLocation.getNodeOffset()) {
return PROCESS_SKIP;
} else if (nodeLocation.getNodeOffset() > ppStmtOffset) {
return PROCESS_ABORT;
}
return PROCESS_CONTINUE;
}
private boolean isCommentOnSameLine(IASTNode node) {
IASTFileLocation fileLocation = node.getFileLocation();
return fileLocation != null && commentLocation.getStartingLineNumber() == fileLocation.getEndingLineNumber();
}
@Override
public int visit(ICPPASTBaseSpecifier baseSpecifier) {
return checkOffsets(baseSpecifier);
}
@Override
public int visit(ICPPASTNamespaceDefinition namespaceDefinition) {
return checkOffsets(namespaceDefinition);
}
@Override
public int visit(ICPPASTTemplateParameter templateParameter) {
return checkOffsets(templateParameter);
}
@Override
public int visit(IASTArrayModifier arrayModifier) {
return checkOffsets(arrayModifier);
}
@Override
public int visit(IASTDeclaration declaration) {
return checkOffsets(declaration);
}
@Override
public int visit(IASTDeclarator declarator) {
return checkOffsets(declarator);
}
@Override
public int visit(IASTDeclSpecifier declSpec) {
return checkOffsets(declSpec);
}
@Override
public int visit(IASTEnumerator enumerator) {
return checkOffsets(enumerator);
}
@Override
public int visit(IASTExpression expression) {
return checkOffsets(expression);
}
@Override
public int visit(IASTInitializer initializer) {
return checkOffsets(initializer);
}
@Override
public int visit(IASTName name) {
return checkOffsets(name);
}
@Override
public int visit(IASTParameterDeclaration parameterDeclaration) {
return checkOffsets(parameterDeclaration);
}
@Override
public int visit(IASTPointerOperator ptrOperator) {
return checkOffsets(ptrOperator);
}
@Override
public int visit(IASTStatement statement) {
return checkOffsets(statement);
}
@Override
public int visit(IASTTranslationUnit tu) {
return checkOffsets(tu);
}
@Override
public int visit(IASTTypeId typeId) {
return checkOffsets(typeId);
}
}
/**
* Creates a NodeCommentMap for the given AST. This is the only way to get a NodeCommentMap
* which contains all the comments mapped against nodes.
*
* @param ast the AST
* @return NodeCommentMap
*/
public static NodeCommentMap getCommentedNodeMap(IASTTranslationUnit ast) {
NodeCommentMap commentMap = new NodeCommentMap();
if (ast == null) {
return commentMap;
}
addCommentsToMap(ast, commentMap);
return commentMap;
}
/**
* Adds all comments given in {@code ast} to the {@code commentMap}. Calling this twice has
* no effect.
*
* @param ast
* the AST which contains the comments to add
* @param commentMap
* the comment map to which the comments are added to
*/
public static void addCommentsToMap(IASTTranslationUnit ast, NodeCommentMap commentMap) {
if (ast == null || commentMap.isASTCovered(ast)) {
return;
}
IASTComment[] commentsArray = ast.getComments();
List<IASTComment> comments = new ArrayList<>(commentsArray.length);
for (IASTComment comment : commentsArray) {
if (comment.isPartOfTranslationUnitFile()) {
comments.add(comment);
}
}
assignPreprocessorComments(commentMap, comments, ast);
CommentHandler commentHandler = new CommentHandler(comments);
ASTCommenterVisitor commenter = new ASTCommenterVisitor(commentHandler, commentMap);
commentMap.setASTCovered(ast);
ast.accept(commenter);
}
private static boolean isCommentDirectlyBeforePreprocessorStatement(IASTComment comment,
IASTPreprocessorStatement statement, IASTTranslationUnit tu) {
if (tu == null || tu.getDeclarations().length == 0) {
return true;
}
IASTFileLocation commentLocation = comment.getFileLocation();
int preprocessorOffset = statement.getFileLocation().getNodeOffset();
if (preprocessorOffset > commentLocation.getNodeOffset()) {
PreprocessorRangeChecker visitor = new PreprocessorRangeChecker(preprocessorOffset, commentLocation);
tu.accept(visitor);
return visitor.isPrePpStmtComment;
}
return false;
}
public static boolean isInWorkspace(IASTNode node) {
return node.isPartOfTranslationUnitFile();
}
/**
* Puts leading and trailing comments to {@code commentMap} and removes them from
* the {@code comments} list.
*/
private static void assignPreprocessorComments(NodeCommentMap commentMap,
List<IASTComment> comments, IASTTranslationUnit tu) {
IASTPreprocessorStatement[] preprocessorStatementsArray = tu.getAllPreprocessorStatements();
if (preprocessorStatementsArray == null) {
return;
}
List<IASTPreprocessorStatement> preprocessorStatements = Arrays.asList(preprocessorStatementsArray);
if (preprocessorStatements.isEmpty() || comments.isEmpty()) {
return;
}
List<IASTComment> freestandingComments = new ArrayList<>(comments.size());
Iterator<IASTPreprocessorStatement> statementsIter = preprocessorStatements.iterator();
Iterator<IASTComment> commentIter = comments.iterator();
IASTPreprocessorStatement curStatement = getNextNodeInTu(statementsIter);
IASTComment curComment = getNextNodeInTu(commentIter);
while (curStatement != null && curComment != null) {
int statementLineNr = curStatement.getFileLocation().getStartingLineNumber();
int commentLineNr = curComment.getFileLocation().getStartingLineNumber();
if (commentLineNr == statementLineNr) {
commentMap.addTrailingCommentToNode(curStatement, curComment);
curComment = getNextNodeInTu(commentIter);
} else if (commentLineNr > statementLineNr) {
curStatement = getNextNodeInTu(statementsIter);
} else if (isCommentDirectlyBeforePreprocessorStatement(curComment, curStatement, tu)) {
commentMap.addLeadingCommentToNode(curStatement, curComment);
curComment = getNextNodeInTu(commentIter);
} else {
freestandingComments.add(curComment);
curComment = getNextNodeInTu(commentIter);
}
}
while (curComment != null) {
freestandingComments.add(curComment);
curComment = getNextNodeInTu(commentIter);
}
if (freestandingComments.size() != comments.size()) {
comments.clear();
comments.addAll(freestandingComments);
}
}
private static <T extends IASTNode> T getNextNodeInTu(Iterator<T> iter) {
while (iter.hasNext()) {
T next = iter.next();
if (next.isPartOfTranslationUnitFile())
return next;
}
return null;
}
}