/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.comments; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map.Entry; import java.util.SortedMap; import java.util.TreeMap; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode; import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessTypeNode; import net.sourceforge.pmd.lang.java.ast.Comment; import net.sourceforge.pmd.lang.java.ast.FormalComment; import net.sourceforge.pmd.lang.java.ast.MultiLineComment; import net.sourceforge.pmd.lang.java.ast.SingleLineComment; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.util.StringUtil; /** * * @author Brian Remedios */ public abstract class AbstractCommentRule extends AbstractJavaRule { protected AbstractCommentRule() { } protected List<Integer> tagsIndicesIn(String comments) { int atPos = comments.indexOf('@'); if (atPos < 0) { return Collections.emptyList(); } List<Integer> ints = new ArrayList<>(); ints.add(atPos); atPos = comments.indexOf('@', atPos + 1); while (atPos >= 0) { ints.add(atPos); atPos = comments.indexOf('@', atPos + 1); } return ints; } protected String filteredCommentIn(Comment comment) { String trimmed = comment.getImage().trim(); if (comment instanceof SingleLineComment) { return singleLineIn(trimmed); } if (comment instanceof MultiLineComment) { return multiLinesIn(trimmed); } if (comment instanceof FormalComment) { return formalLinesIn(trimmed); } return trimmed; // should never reach here } private String singleLineIn(String comment) { if (comment.startsWith("//")) { return comment.substring(2); } return comment; } private static String asSingleString(List<String> lines) { StringBuilder sb = new StringBuilder(); for (String line : lines) { if (StringUtil.isEmpty(line)) { continue; } sb.append(line).append('\n'); } return sb.toString().trim(); } private static String multiLinesIn(String comment) { String[] lines = comment.split("\n"); List<String> filteredLines = new ArrayList<>(lines.length); for (String rawLine : lines) { String line = rawLine.trim(); if (line.endsWith("*/")) { int end = line.length() - 2; int start = line.startsWith("/*") ? 2 : 0; filteredLines.add(line.substring(start, end)); continue; } if (line.length() > 0 && line.charAt(0) == '*') { filteredLines.add(line.substring(1)); continue; } if (line.startsWith("/*")) { filteredLines.add(line.substring(2)); continue; } } return asSingleString(filteredLines); } private String formalLinesIn(String comment) { String[] lines = comment.split("\n"); List<String> filteredLines = new ArrayList<>(lines.length); for (String origLine : lines) { String line = origLine.trim(); if (line.endsWith("*/")) { filteredLines.add(line.substring(0, line.length() - 2)); continue; } if (line.length() > 0 && line.charAt(0) == '*') { filteredLines.add(line.substring(1)); continue; } if (line.startsWith("/**")) { filteredLines.add(line.substring(3)); continue; } } return asSingleString(filteredLines); } protected void assignCommentsToDeclarations(ASTCompilationUnit cUnit) { SortedMap<Integer, Node> itemsByLineNumber = orderedCommentsAndDeclarations(cUnit); FormalComment lastComment = null; AbstractJavaAccessNode lastNode = null; for (Entry<Integer, Node> entry : itemsByLineNumber.entrySet()) { Node value = entry.getValue(); if (value instanceof AbstractJavaAccessNode) { AbstractJavaAccessNode node = (AbstractJavaAccessNode) value; // maybe the last comment is within the last node if (lastComment != null && isCommentNotWithin(lastComment, lastNode, node) && isCommentBefore(lastComment, node)) { node.comment(lastComment); lastComment = null; } if (!(node instanceof AbstractJavaAccessTypeNode)) { lastNode = node; } } else if (value instanceof FormalComment) { lastComment = (FormalComment) value; } } } private boolean isCommentNotWithin(FormalComment n1, Node n2, Node node) { if (n1 == null || n2 == null || node == null) { return true; } boolean isNotWithinNode2 = !(n1.getEndLine() < n2.getEndLine() || n1.getEndLine() == n2.getEndLine() && n1.getEndColumn() < n2.getEndColumn()); boolean isNotSameClass = node.getFirstParentOfType(ASTClassOrInterfaceBody.class) != n2 .getFirstParentOfType(ASTClassOrInterfaceBody.class); boolean isNodeWithinNode2 = (node.getEndLine() < n2.getEndLine() || node.getEndLine() == n2.getEndLine() && node.getEndColumn() < n2.getEndColumn()); return isNotWithinNode2 || isNotSameClass || isNodeWithinNode2; } private boolean isCommentBefore(FormalComment n1, Node n2) { return n1.getEndLine() < n2.getBeginLine() || n1.getEndLine() == n2.getBeginLine() && n1.getEndColumn() < n2.getBeginColumn(); } protected SortedMap<Integer, Node> orderedCommentsAndDeclarations(ASTCompilationUnit cUnit) { SortedMap<Integer, Node> itemsByLineNumber = new TreeMap<>(); List<ASTClassOrInterfaceDeclaration> packageDecl = cUnit .findDescendantsOfType(ASTClassOrInterfaceDeclaration.class); addDeclarations(itemsByLineNumber, packageDecl); addDeclarations(itemsByLineNumber, cUnit.getComments()); List<ASTFieldDeclaration> fields = cUnit.findDescendantsOfType(ASTFieldDeclaration.class); addDeclarations(itemsByLineNumber, fields); List<ASTMethodDeclaration> methods = cUnit.findDescendantsOfType(ASTMethodDeclaration.class); addDeclarations(itemsByLineNumber, methods); List<ASTConstructorDeclaration> constructors = cUnit.findDescendantsOfType(ASTConstructorDeclaration.class); addDeclarations(itemsByLineNumber, constructors); List<ASTEnumDeclaration> enumDecl = cUnit.findDescendantsOfType(ASTEnumDeclaration.class); addDeclarations(itemsByLineNumber, enumDecl); return itemsByLineNumber; } private void addDeclarations(SortedMap<Integer, Node> map, List<? extends Node> nodes) { for (Node node : nodes) { map.put((node.getBeginLine() << 16) + node.getBeginColumn(), node); } } }