blob: 3650c823295bb75992b704361dbb26a10acd80ad [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 Mia-Software and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Sebastien Minguet (Mia-Software) - initial API and implementation
* Frederic Madiot (Mia-Software) - initial API and implementation
* Fabien Giquel (Mia-Software) - initial API and implementation
* Gabriel Barbier (Mia-Software) - initial API and implementation
* Erwan Breton (Sodifrance) - initial API and implementation
* Romain Dervaux (Mia-Software) - initial API and implementation
* Nicolas Bros (Mia-Software) - Bug 335003 - [Discoverer] : Existing Discoverers Refactoring based on new framework
*******************************************************************************/
package org.eclipse.modisco.java.discoverer.internal.io.java;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.SortedMap;
import java.util.TreeMap;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.modisco.java.ASTNode;
import org.eclipse.modisco.java.AbstractTypeDeclaration;
import org.eclipse.modisco.java.Comment;
import org.eclipse.modisco.java.Javadoc;
import org.eclipse.modisco.java.Package;
import org.eclipse.modisco.java.discoverer.internal.io.java.binding.PendingElement;
/**
* The aim of this class is to link comments to the related nodes. JDT does not
* attach comments (line and block comments) to model nodes.
*/
public final class CommentsManager {
private final boolean debug = false;
private static CommentsManager instance = new CommentsManager();
private CommentsManager() {
// Nothing
}
public static void resolveCommentPositions(final JDTVisitor visitor) {
org.eclipse.jdt.core.dom.CompilationUnit cuJdtNode = visitor.getCuNode();
org.eclipse.modisco.java.CompilationUnit moDiscoCuNode = (org.eclipse.modisco.java.CompilationUnit) visitor
.getBijectiveMap().getValue(cuJdtNode);
List<Comment> commentsList = new ArrayList<Comment>(visitor.getCommentsBinding()
.getValues());
CommentsManager manager = CommentsManager.instance;
List<Comment> unLocatedComments = manager.jdtLocationSearch(visitor, cuJdtNode,
moDiscoCuNode, commentsList);
if (CommentsManager.instance.debug) {
System.out.println("===> Comments management <==== " + moDiscoCuNode.getName()); //$NON-NLS-1$
System.out.println("total number of comments = " + commentsList.size()); //$NON-NLS-1$
System.out.println("number of unlocated comments = " + unLocatedComments.size()); //$NON-NLS-1$
System.out
.println("number of comments directly owned by the compilation unit = " + moDiscoCuNode.getComments().size()); //$NON-NLS-1$
}
/*
* to be able to manage correctly the comments directly owned by the
* compilation unit, we will try the alternative algorithm (instead of
* jdt algorithm) to retrieve the correct location of these comments.
* EMF will manage the relocation.
*/
unLocatedComments.addAll(moDiscoCuNode.getComments());
/*
* To manage the comments inside a class declaration:
*
* As all comments inside are generated at the end of the class
* declaration, because we don't store where it comes from (line
* position). So we should try to attach these comments to a better
* element, using the alternative algorithm which handle whitespaces
* differently. EMF will manage the relocation.
*/
for (AbstractTypeDeclaration type : moDiscoCuNode.getTypes()) {
Iterator<Comment> iterator = type.getComments().iterator();
while (iterator.hasNext()) {
Comment comment = iterator.next();
if (comment.isEnclosedByParent()) {
unLocatedComments.add(comment);
}
}
}
// processing remaining comments
for (Comment comment : unLocatedComments) {
if (CommentsManager.instance.debug) {
System.out.println("alternate location for comment: " + comment.getContent()); //$NON-NLS-1$
}
boolean locationFound = CommentsManager.alternateLocationSearch(comment, visitor,
moDiscoCuNode);
/*
* We assure that all comments are processed, in linking remaining
* unlocated comments to root type.
*/
if (!locationFound) {
if (CommentsManager.instance.debug) {
System.out.println("comment location not found: " + comment.getContent()); //$NON-NLS-1$
}
if (visitor.getRootTypeOrEnum() != null) {
visitor.getRootTypeOrEnum().getComments().add(comment);
} else { // misc case of java file whithout type declaration
EcoreUtil.delete(comment);
}
}
}
}
/**
* Linking comments to Nodes using jdt location informations
*
* @param visitor
* @param cuJdtNode
* @param moDiscoCuNode
* @param commentsList
* @return
*/
private List<Comment> jdtLocationSearch(final JDTVisitor visitor,
final org.eclipse.jdt.core.dom.CompilationUnit cuJdtNode,
final org.eclipse.modisco.java.CompilationUnit moDiscoCuNode,
final List<Comment> commentsList) {
List<Comment> localCommentList = new ArrayList<Comment>(commentsList);
SortedMap<Integer, org.eclipse.jdt.core.dom.ASTNode> nodesMap = new TreeMap<Integer, org.eclipse.jdt.core.dom.ASTNode>(
new Comparator<Integer>() {
/**
* We must give an order to nodes which own comments : jdt
* ast nodes gives only starting and ending indexes. e.g.,
* for a root class, start index will be 2 and end will be
* 11, for a method definition, start index will be 4 and
* end will be 6. Ordering will consider first the node
* whose starting index is the highest.
*
* But what if a node has only a trailing comment ? first
* leading comment index = -1 !
*
* Here is a summary of all cases available:
* <table border="1">
* <tr>
* <td>case</td>
* <td>A</td>
* <td>B</td>
* <td>C</td>
* <td>D</td>
* </tr>
* <tr>
* <td>firstLeadingCommentIndex</td>
* <td>-1</td>
* <td>n</td>
* <td>n</td>
* <td>-1</td>
* </tr>
* <tr>
* <td>lastTrailingCommentIndex</td>
* <td>-1</td>
* <td>-1</td>
* <td>n</td>
* <td>n</td>
* </tr>
* </tr>
* </table>
*
* Note: for each case, we cannot gather the information of
* having, or not, comments inside the element (specific to
* elements which have a block ?)
* <ul>
* <li>case A: We have no comments before and after the
* element
*
* <li>case B: We have comments before the element
*
* <li>case C: We have comments before and after the element
*
* <li>case D: We have comments after the element
* </ul>
*
* Warning: a bug has been potentially found ! The start
* position of a node corresponds to the extended start
* position (including comments and whitespace)) but only
* for elements that have a javadoc. So to bypass this bug,
* we have to process comment positions in a different way.
* The order of comments locating should be: comments
* before, comments after, then comments inside.
* <ul>
* <li>for comments before, we will start from the first
* comment, then shift the start position to the end of
* first comment, verify that next comment starts at the new
* start position (end of first comment) otherwise we have
* to consider it is not a comment before.
* <li>For the comments after, we will start from the last
* comment which end position should be the same as the end
* position of the node, then previous comment should end at
* the start position of last comment otherwise it is not a
* comment after.
* <li>Comments inside are comments in the node that are not
* before or after.
* </ul>
*
*/
public int compare(final Integer o1, final Integer o2) {
return -o1.compareTo(o2);
}
});
for (org.eclipse.jdt.core.dom.ASTNode node : visitor.getBijectiveMap().getKeys()) {
/*
* we have to decide if the element has comment
*
* 1. firstLeadingCommentIndex != -1
*
* 2. lastTrailingCommentIndex != -1
*
* 3. start position of a comment in the list is included in the
* range of start position and end position of the element. This
* algorithm may lead to add some elements that have no comments ...
*/
int position = node.getStartPosition();
if (cuJdtNode.firstLeadingCommentIndex(node) != -1) {
nodesMap.put(position, node);
} else if (cuJdtNode.lastTrailingCommentIndex(node) != -1) {
nodesMap.put(position, node);
}
}
/*
* we have now a sorted collection of nodes, using this collection we
* will be able to iterate to link current node with its comments.
*/
for (Integer indexMap : nodesMap.keySet()) {
org.eclipse.jdt.core.dom.ASTNode jdtNode = nodesMap.get(indexMap);
ASTNode element = visitor.getBijectiveMap().getValue(jdtNode);
if (element instanceof PendingElement) {
if (((PendingElement) element).getClientNode() != null) {
element = ((PendingElement) element).getClientNode();
} else { // should never happen
element = visitor.getBijectiveMap().getValue(jdtNode.getParent());
}
}
if (element instanceof Package) {
// replace it by the compilation unit
element = moDiscoCuNode;
} else {
// we have to clear javadoc comments that have been already
// linked to the element
ListIterator<Comment> listIterator = element.getComments().listIterator();
// element.getComments().clear();
while (listIterator.hasNext()) {
Comment comment = listIterator.next();
if (comment instanceof Javadoc) {
// Javadoc javadoc = (Javadoc) comment;
// XXX put back in the un-located comments list?
// localCommentList.add(javadoc);
listIterator.remove();
}
}
}
if (CommentsManager.instance.debug) {
System.out.println("element commented ? " + element.toString()); //$NON-NLS-1$
}
List<Comment> commentsToLink = computeListOfcommentsBefore(jdtNode, cuJdtNode, visitor);
addComments(commentsToLink, element, false, true, localCommentList);
commentsToLink = computeListOfcommentsAfter(jdtNode, cuJdtNode, visitor);
addComments(commentsToLink, element, false, false, localCommentList);
}
// return unlocated comment
return localCommentList;
}
/**
* @param jdtNode
* @param cuJdtNode
* @param visitor
* @return
*/
private List<Comment> computeListOfcommentsAfter(
final org.eclipse.jdt.core.dom.ASTNode jdtNode, final CompilationUnit cuJdtNode,
final JDTVisitor visitor) {
List<Comment> result = new ArrayList<Comment>();
int index = cuJdtNode.lastTrailingCommentIndex(jdtNode);
if (index != -1) {
/*
* we have to retrieve all comments which start position is more
* than basic end position of jdt node. Or the opposite way, all
* comments which ended before the extended end position of jdt
* node.
*/
int endPosition = cuJdtNode.getExtendedStartPosition(jdtNode)
+ cuJdtNode.getExtendedLength(jdtNode);
for (int i = index; i > -1; i--) {
org.eclipse.jdt.core.dom.ASTNode jdtComment = (org.eclipse.jdt.core.dom.ASTNode) cuJdtNode
.getCommentList().get(i);
int commentEndPosition = (cuJdtNode.getExtendedStartPosition(jdtComment) + cuJdtNode
.getExtendedLength(jdtComment));
if (this.debug) {
System.out.println("end position = " + endPosition); //$NON-NLS-1$
System.out.println("comment end position = " + commentEndPosition); //$NON-NLS-1$
}
String whitespaces = null;
if (endPosition > commentEndPosition) {
whitespaces = visitor.getJavaContent()
.substring(commentEndPosition, endPosition).trim();
}
if ((whitespaces == null) || (whitespaces.length() == 0)) {
// shift the end position to manage further comments
endPosition = cuJdtNode.getExtendedStartPosition(jdtComment);
Comment comment = visitor.getCommentsBinding().get(jdtComment);
if (comment != null) {
result.add(0, comment);
}
} else {
// stop iteration (we are inside or before the jdt node)
i = -1;
}
}
}
if (this.debug) {
System.out.println("number of comments after the node = " + result.size()); //$NON-NLS-1$
}
return result;
}
/**
* @param commentsBefore
* @param element
* @param enclosedByElement
* @param prefixOfElement
*/
private void addComments(final List<Comment> commentsBefore, final ASTNode element,
final boolean enclosedByElement, final boolean prefixOfElement,
final List<Comment> localCommentsList) {
for (Comment comment : commentsBefore) {
if (localCommentsList.contains(comment)) {
if (this.debug) {
System.out.println("added comment = " + comment.getContent()); //$NON-NLS-1$
}
comment.setEnclosedByParent(enclosedByElement);
comment.setPrefixOfParent(prefixOfElement);
element.getComments().add(comment);
localCommentsList.remove(comment);
}
}
}
/**
* @param jdtNode
* @param cuJdtNode
* @return
*/
private List<Comment> computeListOfcommentsBefore(
final org.eclipse.jdt.core.dom.ASTNode jdtNode, final CompilationUnit cuJdtNode,
final JDTVisitor visitor) {
List<Comment> result = new ArrayList<Comment>();
int index = cuJdtNode.firstLeadingCommentIndex(jdtNode);
if (index != -1) {
/*
* we have to retrieve all comments which start position is less
* than start position of jdt node. shall we use the extended
* position and length ?
*/
int size = cuJdtNode.getCommentList().size();
int startPosition = cuJdtNode.getExtendedStartPosition(jdtNode);
for (int i = index; i < size; i++) {
org.eclipse.jdt.core.dom.ASTNode jdtComment = (org.eclipse.jdt.core.dom.ASTNode) cuJdtNode
.getCommentList().get(i);
int commentPosition = cuJdtNode.getExtendedStartPosition(jdtComment);
if (this.debug) {
System.out.println("start position = " + startPosition); //$NON-NLS-1$
System.out.println("comment position = " + commentPosition); //$NON-NLS-1$
}
String whitespaces = null;
if (commentPosition > startPosition) {
whitespaces = visitor.getJavaContent()
.substring(startPosition, commentPosition).trim();
}
if ((whitespaces == null) || (whitespaces.length() == 0)) {
/*
* shift the start position to manage further comments, to
* manage whitespace (space and tabulation).
*/
startPosition = commentPosition + cuJdtNode.getExtendedLength(jdtComment);
Comment comment = visitor.getCommentsBinding().get(jdtComment);
if (comment != null) {
result.add(comment);
}
} else {
// stop iteration (we are inside or after the jdt node)
i = size;
}
}
}
if (this.debug) {
System.out.println("number of comments before the node = " + result.size()); //$NON-NLS-1$
}
return result;
}
/**
* @param comment
* @param originalFileContent
* @return content of comment
*/
public static String extractCommentContent(final org.eclipse.jdt.core.dom.Comment comment,
final String originalFileContent) {
String result = originalFileContent.substring(comment.getStartPosition(),
comment.getStartPosition() + comment.getLength());
return result;
}
/**
* Some misc comments are not linked to any node in jdt AST : - comments in
* a block, with at least one blank line before next node and one blank line
* after last node - comments in a block, at the end and with at least one
* blank line after last node. Use another algorithm to link them.
*
* @param comment
* @param visitor
*/
private static boolean alternateLocationSearch(final Comment comment, final JDTVisitor visitor,
final org.eclipse.modisco.java.CompilationUnit moDiscoCuNode) {
ASTNode bestParent = null;
org.eclipse.jdt.core.dom.ASTNode jdtComment = visitor.getCommentsBinding().getKey(comment);
// We search the nearest element in default parent subtree
int bestFollowingNodeStart = Integer.MAX_VALUE;
int bestFollowingNodeEnd = Integer.MIN_VALUE;
int bestEnclosingNodeStart = Integer.MIN_VALUE;
int bestEnclosingNodeEnd = Integer.MAX_VALUE;
for (org.eclipse.jdt.core.dom.ASTNode jdtNode : visitor.getBijectiveMap().getKeys()) {
ASTNode modiscoNode = visitor.getBijectiveMap().get(jdtNode);
if (mayOwnComment(jdtNode) && !(modiscoNode instanceof PendingElement)) {
int sp = jdtNode.getStartPosition();
int ep = jdtNode.getStartPosition() + jdtNode.getLength();
// Does this node follow the comment more closely ?
if ((sp >= jdtComment.getStartPosition() + jdtComment.getLength())
&& (sp < bestFollowingNodeStart || (sp == bestFollowingNodeStart && ep > bestFollowingNodeEnd))
&& (sp < bestEnclosingNodeEnd)) {
bestFollowingNodeStart = sp;
bestFollowingNodeEnd = ep;
bestParent = modiscoNode;
comment.setEnclosedByParent(false);
comment.setPrefixOfParent(true);
}
// Does this node enclose the comment more closely ?
if ((sp <= jdtComment.getStartPosition()) && (ep > jdtComment.getStartPosition())
&& (ep < bestFollowingNodeStart) && (ep <= bestEnclosingNodeEnd)
&& (sp >= bestEnclosingNodeStart)) {
bestEnclosingNodeEnd = ep;
bestEnclosingNodeStart = sp;
bestParent = modiscoNode;
comment.setEnclosedByParent(true);
comment.setPrefixOfParent(false);
}
}
}
if (bestParent != null) {
// insert comment in parent comment list at the right position
if (bestParent instanceof Package) {
bestParent = moDiscoCuNode;
comment.setEnclosedByParent(false);
comment.setPrefixOfParent(true);
}
attachComment(comment, jdtComment, bestParent, visitor);
return true;
}
return false;
}
/*
* Links a comment to a receiver, in making sure of the comments ordering
*/
private static void attachComment(final Comment comment,
final org.eclipse.jdt.core.dom.ASTNode jdtComment, final ASTNode receiver,
final JDTVisitor visitor) {
if (receiver.getComments().contains(comment)) {
return;
}
int insertIndex = 0;
for (; insertIndex < receiver.getComments().size(); insertIndex++) {
org.eclipse.jdt.core.dom.ASTNode jdtOtherComment = visitor.getCommentsBinding().getKey(
receiver.getComments().get(insertIndex));
if (jdtOtherComment != null
&& jdtOtherComment.getStartPosition() > jdtComment.getStartPosition()) {
break;
}
}
receiver.getComments().add(insertIndex, comment);
}
/*
* A comment element (Comment, Tag, ...) can not own others comments (there
* is always an appropriate parent java statement)
*/
private static boolean mayOwnComment(final org.eclipse.jdt.core.dom.ASTNode element) {
return !(element instanceof org.eclipse.jdt.core.dom.Comment)
&& !(element instanceof org.eclipse.jdt.core.dom.TagElement)
&& !(element instanceof org.eclipse.jdt.core.dom.TextElement)
&& !(element instanceof org.eclipse.jdt.core.dom.MemberRef)
&& !(element instanceof org.eclipse.jdt.core.dom.MethodRef);
}
}