/* * Copyright (C) 2007-2010 Júlio Vilmar Gesser. * Copyright (C) 2011, 2013-2016 The JavaParser Team. * * This file is part of JavaParser. * * JavaParser can be used either under the terms of * a) the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * b) the terms of the Apache License * * You should have received a copy of both licenses in LICENCE.LGPL and * LICENCE.APACHE. Please refer to those files for details. * * JavaParser is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. */ package com.github.javaparser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.comments.Comment; import com.github.javaparser.ast.comments.LineComment; import com.github.javaparser.utils.PositionUtils; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.TreeSet; import java.util.stream.Collectors; import static com.github.javaparser.ast.Node.NODE_BY_BEGIN_POSITION; /** * Assigns comments to nodes of the AST. * * @author Sebastian Kuerten * @author Júlio Vilmar Gesser */ class CommentsInserter { private final ParserConfiguration configuration; CommentsInserter(ParserConfiguration configuration) { this.configuration = configuration; } /** * Comments are attributed to the thing they comment and are removed from * the comments. */ private void insertComments(CompilationUnit cu, TreeSet comments) { if (comments.isEmpty()) return; /* I should sort all the direct children and the comments, if a comment is the first thing then it a comment to the CompilationUnit */ // FIXME if there is no package it could be also a comment to the following class... // so I could use some heuristics in these cases to distinguish the two // cases List children = cu.getChildNodes(); Comment firstComment = comments.iterator().next(); if (cu.getPackageDeclaration().isPresent() && (children.isEmpty() || PositionUtils.areInOrder( firstComment, cu.getPackageDeclaration().get()))) { cu.setComment(firstComment); comments.remove(firstComment); } } /** * This method try to attributes the nodes received to child of the node. It * returns the node that were not attributed. */ void insertComments(Node node, TreeSet commentsToAttribute) { if (commentsToAttribute.isEmpty()) return; if (node instanceof CompilationUnit) { insertComments((CompilationUnit) node, commentsToAttribute); } // the comments can: // 1) Inside one of the child, then it is the child that have to // associate them // 2) If they are not inside a child they could be preceeding nothing, a // comment or a child // if they preceed a child they are assigned to it, otherweise they // remain "orphans" List children = node.getChildNodes(); for (Node child : children) { TreeSet commentsInsideChild = new TreeSet<>(NODE_BY_BEGIN_POSITION); commentsInsideChild.addAll( commentsToAttribute.stream() .filter(c -> c.getRange().isPresent()) .filter(c -> PositionUtils.nodeContains(child, c, configuration.isDoNotConsiderAnnotationsAsNodeStartForCodeAttribution())).collect(Collectors.toList())); commentsToAttribute.removeAll(commentsInsideChild); insertComments(child, commentsInsideChild); } attributeLineCommentsOnSameLine(commentsToAttribute, children); /* at this point I create an ordered list of all remaining comments and children */ Comment previousComment = null; final List attributedComments = new LinkedList<>(); List childrenAndComments = new LinkedList<>(); // Avoid attributing comments to a meaningless container. childrenAndComments.addAll(children); commentsToAttribute.removeAll(attributedComments); childrenAndComments.addAll(commentsToAttribute); PositionUtils.sortByBeginPosition(childrenAndComments, configuration.isDoNotConsiderAnnotationsAsNodeStartForCodeAttribution()); for (Node thing : childrenAndComments) { if (thing instanceof Comment) { previousComment = (Comment) thing; if (!previousComment.isOrphan()) { previousComment = null; } } else { if (previousComment != null && !thing.getComment().isPresent()) { if (!configuration.isDoNotAssignCommentsPrecedingEmptyLines() || !thereAreLinesBetween(previousComment, thing)) { thing.setComment(previousComment); attributedComments.add(previousComment); previousComment = null; } } } } commentsToAttribute.removeAll(attributedComments); // all the remaining are orphan nodes for (Comment c : commentsToAttribute) { if (c.isOrphan()) { node.addOrphanComment(c); } } } private void attributeLineCommentsOnSameLine(TreeSet commentsToAttribute, List children) { /* I can attribute in line comments to elements preceeding them, if there is something contained in their line */ List attributedComments = new LinkedList<>(); commentsToAttribute.stream() .filter(comment -> comment.getRange().isPresent()) .filter(Comment::isLineComment) .forEach(comment -> children.stream() .filter(child -> child.getRange().isPresent()) .forEach(child -> { Range commentRange = comment.getRange().get(); Range childRange = child.getRange().get(); if (childRange.end.line == commentRange.begin.line && attributeLineCommentToNodeOrChild(child, comment.asLineComment())) { attributedComments.add(comment); } })); commentsToAttribute.removeAll(attributedComments); } private boolean attributeLineCommentToNodeOrChild(Node node, LineComment lineComment) { if (!node.getRange().isPresent() || !lineComment.getRange().isPresent()) { return false; } // The node start and end at the same line as the comment, // let's give to it the comment if (node.getBegin().get().line == lineComment.getBegin().get().line && !node.getComment().isPresent()) { if (!(node instanceof Comment)) { node.setComment(lineComment); } return true; } else { // try with all the children, sorted by reverse position (so the // first one is the nearest to the comment List children = new LinkedList<>(); children.addAll(node.getChildNodes()); PositionUtils.sortByBeginPosition(children); Collections.reverse(children); for (Node child : children) { if (attributeLineCommentToNodeOrChild(child, lineComment)) { return true; } } return false; } } private boolean thereAreLinesBetween(Node a, Node b) { if (!a.getRange().isPresent() || !b.getRange().isPresent()) { return true; } if (!PositionUtils.areInOrder(a, b)) { return thereAreLinesBetween(b, a); } int endOfA = a.getEnd().get().line; return b.getBegin().get().line > endOfA + 1; } }