/******************************************************************************* * 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; } }