/*******************************************************************************
* Copyright (c) 2008, 2009 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
******************************************************************************/
package org.eclipse.cdt.internal.core.dom.rewrite.commenthandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.TreeMap;
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.IASTExpression;
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.IASTEnumerationSpecifier.IASTEnumerator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier;
import org.eclipse.cdt.internal.core.dom.parser.ASTNode;
import org.eclipse.cdt.internal.core.dom.rewrite.util.OffsetHelper;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
/**
* This is the starting point of the entire comment handling process. The creation of the
* NodeCommentMap is based on the IASTTranslationUnit. From this TranslationUnit the comments
* are extracted and skipped if they belong not to the same workspace. An ASTCommenterVisitor
* is initialized with this collection of comments. And the visit process can start.
*
* @see org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommenter
* @see org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap
*
* @author Guido Zgraggen IFS
*/
public class ASTCommenter {
private static final class PPRangeChecker extends ASTVisitor {
int ppOffset;
int commentOffset;
boolean isPrePPComment = true;
private PPRangeChecker(boolean visitNodes, int nextPPOfset, int commentNodeOffset) {
super(visitNodes);
ppOffset = nextPPOfset;
commentOffset = commentNodeOffset;
}
private int checkOffsets(IASTNode node) {
int offset = ((ASTNode)node).getOffset();
int status = ASTVisitor.PROCESS_CONTINUE;
if (offset > commentOffset && offset < ppOffset) {
isPrePPComment = false;
status = ASTVisitor.PROCESS_ABORT;
} else if ((offset + ((ASTNode)node).getLength() < commentOffset)) {
status = ASTVisitor.PROCESS_SKIP;
} else if (offset > ppOffset) {
status = ASTVisitor.PROCESS_ABORT;
}
return status;
}
@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 TranslationUnit. This is the only way
* to get a NodeCommentMap which contains all the comments mapped against nodes.
*
* @param transUnit TranslationUnit
* @return NodeCommentMap
*/
public static NodeCommentMap getCommentedNodeMap(IASTTranslationUnit transUnit){
if (transUnit== null) {
return new NodeCommentMap();
}
ArrayList<IASTComment> comments = removeNotNeededComments(transUnit);
if (comments == null || comments.size() == 0) {
return new NodeCommentMap();
}
return addCommentsToCommentMap(transUnit, comments);
}
private static ArrayList<IASTComment> removeNotNeededComments(IASTTranslationUnit transUnit) {
ArrayList<IASTComment> comments = getCommentsInWorkspace(transUnit);
if (comments == null || comments.size() == 0) {
return null;
}
ArrayList<IASTComment> com = removeAllPreprocessorComments(transUnit, comments);
return com;
}
private static ArrayList<IASTComment> getCommentsInWorkspace(IASTTranslationUnit tu) {
IASTComment[] comments = tu.getComments();
ArrayList<IASTComment> commentsInWorkspace = new ArrayList<IASTComment>();
if (comments == null || comments.length == 0) {
return null;
}
for (IASTComment comment : comments) {
if (isInWorkspace(comment)) {
commentsInWorkspace.add(comment);
}
}
return commentsInWorkspace;
}
private static ArrayList<IASTComment> removeAllPreprocessorComments(IASTTranslationUnit tu,
ArrayList<IASTComment> comments) {
IASTPreprocessorStatement[] preprocessorStatements = tu.getAllPreprocessorStatements();
TreeMap<Integer, String> treeOfPreProcessorLines = new TreeMap<Integer,String>();
TreeMap<String, ArrayList<Integer>> ppOffsetForFiles = new TreeMap<String, ArrayList<Integer>>();
for (IASTPreprocessorStatement statement : preprocessorStatements) {
if (isInWorkspace(statement)) {
String fileName = statement.getFileLocation().getFileName();
treeOfPreProcessorLines.put(OffsetHelper.getStartingLineNumber(statement), fileName);
ArrayList<Integer> offsetList = ppOffsetForFiles.get(fileName);
if (offsetList == null) {
offsetList = new ArrayList<Integer>();
ppOffsetForFiles.put(fileName, offsetList);
}
offsetList.add(((ASTNode)statement).getOffset());
}
}
ArrayList<IASTComment> commentsInCode = new ArrayList<IASTComment>();
for (IASTComment comment : comments) {
int comStartLineNumber = OffsetHelper.getStartingLineNumber(comment);
String fileName = comment.getFileLocation().getFileName();
if (treeOfPreProcessorLines.containsKey(comStartLineNumber)
&& treeOfPreProcessorLines.get(comStartLineNumber).equals(fileName)) {
continue;
}
if (commentIsAtTheBeginningBeforePreprocessorStatements(comment,
ppOffsetForFiles.get(fileName), tu)) {
continue;
}
commentsInCode.add(comment);
}
return commentsInCode;
}
private static boolean commentIsAtTheBeginningBeforePreprocessorStatements(IASTComment comment,
ArrayList<Integer> listOfPreProcessorOffset, IASTTranslationUnit tu) {
if (listOfPreProcessorOffset == null) {
return false;
}
if (comment.getTranslationUnit()==null || comment.getTranslationUnit().getDeclarations().length < 1) {
return true;
}
IASTDeclaration decl = comment.getTranslationUnit().getDeclarations()[0];
String commentFileName = comment.getFileLocation().getFileName();
boolean sameFile = decl.getFileLocation().getFileName().equals(commentFileName);
int commentNodeOffset = ((ASTNode)comment).getOffset();
if (sameFile) {
if (decl.getFileLocation().getNodeOffset() < commentNodeOffset) {
return false;
}
}
Collections.sort(listOfPreProcessorOffset);
int nextPPOfset = -1;
for (Integer integer : listOfPreProcessorOffset) {
if (integer > commentNodeOffset) {
nextPPOfset = integer;
PPRangeChecker visti = new PPRangeChecker(true, nextPPOfset, commentNodeOffset);
tu.accept(visti);
if (visti.isPrePPComment) {
return true;
}
}
}
return false;
}
private static boolean isInWorkspace(IASTNode node) {
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
IPath nodePath = new Path(node.getContainingFilename());
for (IProject project : projects) {
if (project.getLocation().isPrefixOf(nodePath)) return true;
}
return false;
}
private static NodeCommentMap addCommentsToCommentMap(IASTTranslationUnit rootNode,
ArrayList<IASTComment> comments){
NodeCommentMap commentMap = new NodeCommentMap();
CommentHandler commHandler = new CommentHandler(comments);
IASTDeclaration[] declarations = rootNode.getDeclarations();
for (int i = 0; i < declarations.length; i++) {
if (isInWorkspace(declarations[i])) {
ASTCommenterVisitor commenter = new ASTCommenterVisitor(commHandler, commentMap);
declarations[i].accept(commenter);
//add remaining comments to the last declaration => Comments won't get lost
if (i + 1 == declarations.length) {
commenter.addRemainingComments(declarations[i]);
}
}
}
return commentMap;
}
}