/** * Copyright (C) 2006-2017 INRIA and contributors * Spoon - http://spoon.gforge.inria.fr/ * * This software is governed by the CeCILL-C License under French law and * abiding by the rules of distribution of free software. You can use, modify * and/or redistribute the software under the terms of the CeCILL-C license as * circulated by CEA, CNRS and INRIA at http://www.cecill.info. * * This program 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 CeCILL-C License for more details. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-C license and that you accept its terms. */ package spoon.support.compiler.jdt; import org.apache.log4j.Logger; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; import spoon.reflect.code.CtBinaryOperator; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtBodyHolder; import spoon.reflect.code.CtCase; import spoon.reflect.code.CtCatch; import spoon.reflect.code.CtComment; import spoon.reflect.code.CtConditional; import spoon.reflect.code.CtIf; import spoon.reflect.code.CtNewArray; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtStatementList; import spoon.reflect.code.CtSwitch; import spoon.reflect.cu.CompilationUnit; import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.CtAnonymousExecutable; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtInterface; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtParameter; import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.declaration.ParentNotInitializedException; import spoon.reflect.factory.Factory; import spoon.reflect.visitor.CtInheritanceScanner; import spoon.reflect.visitor.CtScanner; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * The comment builder that will insert all element of a CompilationUnitDeclaration into the Spoon AST */ @SuppressWarnings("unchecked") class JDTCommentBuilder { private static final Logger LOGGER = Logger.getLogger(JDTCommentBuilder.class); private final CompilationUnitDeclaration declarationUnit; private String filePath; private CompilationUnit spoonUnit; private Factory factory; private ICompilationUnit sourceUnit; private char[] contents; /** * Creates a JDTCommentBuilder that will insert all comment of the declarationUnit into the Spoon AST * @param declarationUnit the declaration unit * @param factory the Spoon AST */ JDTCommentBuilder(CompilationUnitDeclaration declarationUnit, Factory factory) { this.declarationUnit = declarationUnit; if (declarationUnit.comments == null) { return; } this.factory = factory; this.sourceUnit = declarationUnit.compilationResult.compilationUnit; this.contents = sourceUnit.getContents(); this.filePath = CharOperation.charToString(sourceUnit.getFileName()); this.spoonUnit = factory.CompilationUnit().create(filePath); } /** * Start the build process */ public void build() { if (declarationUnit.comments == null) { return; } for (int j = 0; j < declarationUnit.comments.length; j++) { int[] positions = declarationUnit.comments[j]; buildComment(positions); } } /** * Inserts the comment at the position positions in the AST * @param positions the position of the comment */ private void buildComment(int[] positions) { int start = positions[0]; int end = -positions[1]; CtComment comment = factory.Core().createComment(); // comment.setCommentType(CtComment.CommentType.BLOCK); // the inline comments have negative start if (start < 0) { comment.setCommentType(CtComment.CommentType.INLINE); start = -start; } // Javadoc comments have negative end position if (end <= 0) { comment.setCommentType(CtComment.CommentType.JAVADOC); end = -end; } String commentContent = getCommentContent(start, end); int[] lineSeparatorPositions = declarationUnit.compilationResult.lineSeparatorPositions; SourcePosition sourcePosition = factory.Core().createSourcePosition(spoonUnit, start, end, lineSeparatorPositions); // create the Spoon comment element comment.setContent(commentContent); comment.setPosition(sourcePosition); insertCommentInAST(comment); } /** * Insert the element to nearer element in the elements collections * @param comment the comment to insert * @param elements the collection that content the ast elements * @return */ private CtElement addCommentToNear(final CtComment comment, final Collection<CtElement> elements) { CtElement best = null; int smallDistance = Integer.MAX_VALUE; for (CtElement element : elements) { if (element.getPosition() == null) { continue; } if (element.isImplicit()) { continue; } if (element instanceof CtComment) { continue; } final boolean isAfter = element.getPosition().getSourceEnd() < comment.getPosition().getSourceStart(); int distance = Math.abs(element.getPosition().getSourceStart() - comment.getPosition().getSourceEnd()); if (isAfter) { distance = Math.abs(element.getPosition().getSourceEnd() - comment.getPosition().getSourceStart()); } int elementEndLine = element.getPosition().getEndLine(); int commentLine = comment.getPosition().getLine(); if (distance < smallDistance && (!isAfter || elementEndLine == commentLine)) { best = element; smallDistance = distance; } } // adds the comment to the nearest element if (best != null) { best.addComment(comment); } return best; } /** * Inserts the comment into the AST. * @param comment the comment to insert */ private void insertCommentInAST(final CtComment comment) { CtElement commentParent = findCommentParent(comment); if (commentParent == null) { File file = spoonUnit.getFile(); if (file != null && file.getName().equals(DefaultJavaPrettyPrinter.JAVA_PACKAGE_DECLARATION)) { spoonUnit.getDeclaredPackage().addComment(comment); } else { comment.setCommentType(CtComment.CommentType.FILE); addCommentToNear(comment, new ArrayList<CtElement>(spoonUnit.getDeclaredTypes())); } return; } // visitor that inserts the comment in the element CtInheritanceScanner insertionVisitor = new CtInheritanceScanner() { private boolean isScanned = false; @Override public void scan(CtElement e) { if (e == null) { return; } // Do not visit the AST, only the first element if (!isScanned) { isScanned = true; if (e.getPosition().getSourceStart() == comment.getPosition().getSourceStart()) { e.addComment(comment); return; } super.scan(e); } } @Override public <R> void visitCtStatementList(CtStatementList e) { addCommentToNear(comment, new ArrayList<CtElement>(e.getStatements())); try { comment.getParent(); } catch (ParentNotInitializedException ex) { e.addStatement(comment); } } @Override public <T> void visitCtMethod(CtMethod<T> e) { e.addComment(comment); } @Override public <T> void visitCtConstructor(CtConstructor<T> e) { e.addComment(comment); } @Override public <T> void visitCtConditional(CtConditional<T> e) { List<CtElement> elements = new ArrayList<>(); elements.add(e.getElseExpression()); elements.add(e.getThenExpression()); elements.add(e.getCondition()); addCommentToNear(comment, elements); } @Override public <T> void visitCtBinaryOperator(CtBinaryOperator<T> e) { List<CtElement> elements = new ArrayList<>(); elements.add(e.getLeftHandOperand()); elements.add(e.getRightHandOperand()); addCommentToNear(comment, elements); } @Override public <T> void visitCtClass(CtClass<T> e) { if (comment.getPosition().getLine() <= e.getPosition().getLine()) { e.addComment(comment); return; } final List<CtElement> elements = new ArrayList<>(); for (CtTypeMember typeMember : e.getTypeMembers()) { if (typeMember instanceof CtField || typeMember instanceof CtMethod || typeMember instanceof CtConstructor) { elements.add(typeMember); } } addCommentToNear(comment, elements); try { comment.getParent(); } catch (ParentNotInitializedException ex) { e.addComment(comment); } } @Override public <T> void visitCtInterface(CtInterface<T> e) { final List<CtElement> elements = new ArrayList<>(); for (CtTypeMember typeMember : e.getTypeMembers()) { if (typeMember instanceof CtField || typeMember instanceof CtMethod) { elements.add(typeMember); } } addCommentToNear(comment, elements); try { comment.getParent(); } catch (ParentNotInitializedException ex) { e.addComment(comment); } } @Override public <T> void visitCtField(CtField<T> e) { e.addComment(comment); } @Override public <E> void visitCtSwitch(CtSwitch<E> e) { List<CtCase<? super E>> cases = e.getCases(); CtCase previous = null; for (int i = 0; i < cases.size(); i++) { CtCase<? super E> ctCase = cases.get(i); if (previous == null) { if (comment.getPosition().getSourceStart() < ctCase.getPosition().getSourceStart() && e.getPosition().getSourceStart() < comment.getPosition().getSourceStart()) { ctCase.addComment(comment); return; } } else { if (previous.getPosition().getSourceEnd() < comment.getPosition().getSourceStart() && ctCase.getPosition().getSourceStart() > comment.getPosition().getSourceStart()) { addCommentToNear(comment, new ArrayList<CtElement>(previous.getStatements())); try { comment.getParent(); } catch (ParentNotInitializedException ex) { previous.addStatement(comment); } return; } } previous = ctCase; } if (previous.getPosition().getSourceEnd() < comment.getPosition().getSourceStart()) { addCommentToNear(comment, new ArrayList<CtElement>(previous.getStatements())); try { comment.getParent(); } catch (ParentNotInitializedException ex) { previous.addStatement(comment); } return; } try { comment.getParent(); } catch (ParentNotInitializedException ex) { e.addComment(comment); } } @Override public void visitCtIf(CtIf e) { if (!(e.getThenStatement() instanceof CtBlock)) { if (comment.getPosition().getSourceEnd() <= e.getThenStatement().getPosition().getSourceStart()) { e.getThenStatement().addComment(comment); return; } } if (e.getElseStatement() != null) { SourcePosition thenPosition = e.getThenStatement().getPosition() == null ? ((CtBlock) e.getThenStatement()).getStatement(0).getPosition() : e.getThenStatement().getPosition(); SourcePosition elsePosition = e.getElseStatement().getPosition() == null ? ((CtBlock) e.getElseStatement()).getStatement(0).getPosition() : e.getElseStatement().getPosition(); if (comment.getPosition().getSourceStart() > thenPosition.getSourceEnd() && comment.getPosition().getSourceEnd() < elsePosition.getSourceStart()) { e.getElseStatement().addComment(comment); } } try { comment.getParent(); } catch (ParentNotInitializedException ex) { e.addComment(comment); } } @Override public void scanCtStatement(CtStatement s) { if (!(s instanceof CtStatementList || s instanceof CtSwitch)) { s.addComment(comment); } } @Override public void visitCtAnonymousExecutable(CtAnonymousExecutable e) { e.addComment(comment); } @Override public <T> void visitCtNewArray(CtNewArray<T> e) { addCommentToNear(comment, new ArrayList<CtElement>(e.getElements())); try { comment.getParent(); } catch (ParentNotInitializedException ex) { e.addComment(comment); } } @Override public <T> void visitCtParameter(CtParameter<T> e) { e.addComment(comment); } @Override public void visitCtCatch(CtCatch e) { if (comment.getPosition().getLine() <= e.getPosition().getLine()) { e.addComment(comment); return; } } }; insertionVisitor.scan(commentParent); try { comment.getParent(); } catch (ParentNotInitializedException e) { LOGGER.error(comment + " is not added into the AST", e); } } /** * Find the parent of a comment based on the position * @param comment the comment * @return the parent of the comment */ private CtElement findCommentParent(CtComment comment) { class FindCommentParentScanner extends CtScanner { public CtElement commentParent; private int start; private int end; FindCommentParentScanner(int start, int end) { this.start = start; this.end = end; } @Override public void scan(CtElement element) { if (element == null) { return; } if (element.isImplicit()) { return; } CtElement body = getBody(element); if (body != null && body.getPosition() == null) { body = null; } if (element.getPosition() != null && ((element.getPosition().getSourceStart() <= start && element.getPosition().getSourceEnd() >= end) || (body != null && (body.getPosition().getSourceStart() <= start && body.getPosition().getSourceEnd() >= end)))) { commentParent = element; element.accept(this); } } } FindCommentParentScanner findCommentParentScanner = new FindCommentParentScanner( comment.getPosition().getSourceStart(), comment.getPosition().getSourceEnd()); findCommentParentScanner.scan(spoonUnit.getDeclaredTypes()); return findCommentParentScanner.commentParent; } /** * @param element * @return body of element or null if this element has no body */ static CtElement getBody(CtElement e) { if (e instanceof CtBodyHolder) { return ((CtBodyHolder) e).getBody(); } return null; } /** * Extract the comment from the content of the class * @param start the start position of the comment * @param end the end position of the comment * @return the content of the comment */ private String getCommentContent(int start, int end) { //skip comment prefix start += 2; return cleanComment(new String(contents, start, end - start)); } public static String cleanComment(String comment) { StringBuffer ret = new StringBuffer(); String[] lines = comment.split("\n"); // limit case if (lines.length == 1) { return lines[0].replaceAll("^/\\*+ ?", "").replaceAll("\\*+/$", "").trim(); } for (String s : lines) { String cleanUpLine = s.trim(); if (cleanUpLine.startsWith("/**")) { cleanUpLine = cleanUpLine.replaceAll("/\\*+ ?", ""); } else if (cleanUpLine.endsWith("*/")) { cleanUpLine = cleanUpLine.replaceAll("\\*+/$", "").replaceAll("^[ \t]*\\*+ ?", ""); } else { cleanUpLine = cleanUpLine.replaceAll("^[ \t]*\\*+ ?", ""); } ret.append(cleanUpLine); ret.append("\n"); } // clean '\r' StringBuffer ret2 = new StringBuffer(); for (int i = 0; i < ret.length(); i++) { if (ret.charAt(i) != '\r') { ret2.append(ret.charAt(i)); } } return ret2.toString().trim(); } }