Issue
TL;DR: How to preserve the Javadoc, line- and block-comment when creating a new java type based on a given type?
Long:
I am batch converting an infinite amount of types (Java classes) based on an unwanted base class towards Java enum types by using a headless eclipse application using JDT. My approach is to create a new EnumDeclaration
based on the type information and adding the EnumConstantDeclaration
s based on the FieldDeclaration
s (exluding the serialVersionUID
) of the initial class type. After that I add the MethodDeclaration
s (excluding the constructor) by simply adding a clone of the original MethodDeclaration
s to the BodyDelcaration
of the newly created EnumDeclaration
. Once I have done that, which is quite straight forward thanks to the great API, I do the following...
// create the EnumDeclaration from the given UnwantedClass CompilationUnit
final EnumDeclaration enumTypeDeclaration = createEnumDeclaration(cu, astRoot, methodDeclarations, ast);
// Find the original UnwantedClass TypeDeclaration and replace it with the new EnumDeclaration
astRoot.accept(new ASTVisitor() {
@Override
public boolean visit(final TypeDeclaration node) {
rewriter.replace(node, enumTypeDeclaration, null);
return false;
}
});
...to replace the original Java class Type
with the new EnumDeclaration
. This works almost perfectly. The only thing missing are all the Line
-, Block
- and JavadocComment
elements of the original Java type. I found out that you can at least retrieve all Comment
instances by:
List<Comment> comments = cu.getCommentList();
if (comments != null) {
for (Comment comment : comments) {
comment.accept(visitor);
}
}
That gives me all the comment, but I haven't figured out how to map a Comment
instance to a BodyDeclaration
, because these Comment
instances are basically free floating all over the Source
file and are only linked by their startPosition within the Source
file.
There is the getAlternateRoot
method, but I haven't managed either to utilize that one.
The question is: How do I preserve the Comment
instances from the original type and put them at the correct position in the new type?
Solution
It seems, there is no straight forward approach to solve this, because javadoc (or just comments in general) in java files have no direct relation to the actual code they are meant to comment. Its just common sense among developers that we are usually put the comment above the code we are commenting. So I took the following path to solve this:
- Create an starting position based index which maps an Integer (the starting position) to so called CommentEntry instances, which is basically a tuple consisting of the org.eclipse.jdt.core.dom.Comment and the actual text, which I extract by simply substring the source string of the compilation unit.
- I visit my original CompilationUnit and retrieve all relevant ASTNode, which are Type-, Field- and MethodDeclaration instances in my case and map those on their starting positions.
- Now I iterate over all mapped CommentEntry positions from 1. and associate them with an ASTNode instance, which is mapped to the same position (from the map of 2.). The result is then a map with ASTNode -> CommentEntry.
- While building my new EnumDeclaration I query the map from 3. either by using the actual ASTNode type (TypeDeclaration only exists once), the actual instance of the ASTNode (I use attributes from the orignal FieldDeclarations to create the EnumConstants), or use the ASTMatcher to identify the correct MethodDeclaration, because I had to clone these in order to be able to "copy" them into the new EnumDeclaration Body section.
The adding of the comment isn't as straight forward as I thought, because in newer JSL you cant simply set the comment String in javadoc (only supported in JSL2, but in this version we hadn't enums). So, I used the following code:
private void setJavadoc(final BodyDeclaration bodyDeclaration, final CommentEntry commentEntry) {
final Javadoc javadoc = (Javadoc) ASTNode.copySubtree(ast, commentEntry.getComment());
final TagElement tagElement = ast.newTagElement();
final TextElement textElement = ast.newTextElement();
textElement.setText(commentEntry.getText());
tagElement.fragments().add(textElement);
javadoc.tags().add(tagElement);
bodyDeclaration.setJavadoc(javadoc);
}
As you can see, the actual comment text can be simply added by using a TextElement, even though it contains Tags. To make it perfect you may need to substring the comment text previously extracted from the source file, because it will contain the /** and */.
Answered By - user166566