View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.comments;
5   
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.List;
9   import java.util.Map.Entry;
10  import java.util.SortedMap;
11  import java.util.TreeMap;
12  
13  import net.sourceforge.pmd.lang.ast.Node;
14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody;
15  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
16  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
17  import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
18  import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
19  import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
20  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
21  import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode;
22  import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessTypeNode;
23  import net.sourceforge.pmd.lang.java.ast.Comment;
24  import net.sourceforge.pmd.lang.java.ast.FormalComment;
25  import net.sourceforge.pmd.lang.java.ast.MultiLineComment;
26  import net.sourceforge.pmd.lang.java.ast.SingleLineComment;
27  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
28  import net.sourceforge.pmd.util.StringUtil;
29  
30  /**
31   * 
32   * @author Brian Remedios
33   */
34  public abstract class AbstractCommentRule extends AbstractJavaRule {
35  
36      protected AbstractCommentRule() {
37      }
38  
39      protected List<Integer> tagsIndicesIn(String comments) {
40  
41          int atPos = comments.indexOf('@');
42          if (atPos < 0) {
43              return Collections.emptyList();
44          }
45  
46          List<Integer> ints = new ArrayList<>();
47          ints.add(atPos);
48  
49          atPos = comments.indexOf('@', atPos + 1);
50          while (atPos >= 0) {
51              ints.add(atPos);
52              atPos = comments.indexOf('@', atPos + 1);
53          }
54  
55          return ints;
56      }
57  
58      protected String filteredCommentIn(Comment comment) {
59  
60          String trimmed = comment.getImage().trim();
61  
62          if (comment instanceof SingleLineComment) {
63              return singleLineIn(trimmed);
64          }
65          if (comment instanceof MultiLineComment) {
66              return multiLinesIn(trimmed);
67          }
68          if (comment instanceof FormalComment) {
69              return formalLinesIn(trimmed);
70          }
71  
72          return trimmed; // should never reach here
73      }
74  
75      private String singleLineIn(String comment) {
76  
77          if (comment.startsWith("//")) {
78              return comment.substring(2);
79          }
80  
81          return comment;
82      }
83  
84      private static String asSingleString(List<String> lines) {
85  
86          StringBuilder sb = new StringBuilder();
87          for (String line : lines) {
88              if (StringUtil.isEmpty(line)) {
89                  continue;
90              }
91              sb.append(line).append('\n');
92          }
93  
94          return sb.toString().trim();
95      }
96  
97      private static String multiLinesIn(String comment) {
98  
99          String[] lines = comment.split("\n");
100         List<String> filteredLines = new ArrayList<>(lines.length);
101 
102         for (String rawLine : lines) {
103             String line = rawLine.trim();
104 
105             if (line.endsWith("*/")) {
106                 int end = line.length() - 2;
107                 int start = line.startsWith("/*") ? 2 : 0;
108                 filteredLines.add(line.substring(start, end));
109                 continue;
110             }
111 
112             if (line.length() > 0 && line.charAt(0) == '*') {
113                 filteredLines.add(line.substring(1));
114                 continue;
115             }
116 
117             if (line.startsWith("/*")) {
118                 filteredLines.add(line.substring(2));
119                 continue;
120             }
121 
122         }
123 
124         return asSingleString(filteredLines);
125     }
126 
127     private String formalLinesIn(String comment) {
128 
129         String[] lines = comment.split("\n");
130         List<String> filteredLines = new ArrayList<>(lines.length);
131 
132         for (String line : lines) {
133             line = line.trim();
134 
135             if (line.endsWith("*/")) {
136                 filteredLines.add(line.substring(0, line.length() - 2));
137                 continue;
138             }
139 
140             if (line.length() > 0 && line.charAt(0) == '*') {
141                 filteredLines.add(line.substring(1));
142                 continue;
143             }
144             if (line.startsWith("/**")) {
145                 filteredLines.add(line.substring(3));
146                 continue;
147             }
148 
149         }
150 
151         return asSingleString(filteredLines);
152     }
153 
154     protected void assignCommentsToDeclarations(ASTCompilationUnit cUnit) {
155 
156         SortedMap<Integer, Node> itemsByLineNumber = orderedCommentsAndDeclarations(cUnit);
157         FormalComment lastComment = null;
158         AbstractJavaAccessNode lastNode = null;
159 
160         for (Entry<Integer, Node> entry : itemsByLineNumber.entrySet()) {
161             Node value = entry.getValue();
162 
163             if (value instanceof AbstractJavaAccessNode) {
164                 AbstractJavaAccessNode node = (AbstractJavaAccessNode) value;
165 
166                 // maybe the last comment is within the last node
167                 if (lastComment != null && isCommentNotWithin(lastComment, lastNode, node)
168                         && isCommentBefore(lastComment, node)) {
169                     node.comment(lastComment);
170                     lastComment = null;
171                 }
172                 if (!(node instanceof AbstractJavaAccessTypeNode)) {
173                     lastNode = node;
174                 }
175             } else if (value instanceof FormalComment) {
176                 lastComment = (FormalComment) value;
177             }
178         }
179     }
180 
181     private boolean isCommentNotWithin(FormalComment n1, Node n2, Node node) {
182         if (n1 == null || n2 == null || node == null) {
183             return true;
184         }
185         boolean isNotWithinNode2 = !(n1.getEndLine() < n2.getEndLine() || n1.getEndLine() == n2.getEndLine()
186                 && n1.getEndColumn() < n2.getEndColumn());
187         boolean isNotSameClass = node.getFirstParentOfType(ASTClassOrInterfaceBody.class) != n2
188                 .getFirstParentOfType(ASTClassOrInterfaceBody.class);
189         return isNotWithinNode2 || isNotSameClass;
190     }
191 
192     private boolean isCommentBefore(FormalComment n1, Node n2) {
193         return n1.getEndLine() < n2.getBeginLine() || n1.getEndLine() == n2.getBeginLine()
194                 && n1.getEndColumn() < n2.getBeginColumn();
195     }
196 
197     protected SortedMap<Integer, Node> orderedCommentsAndDeclarations(ASTCompilationUnit cUnit) {
198 
199         SortedMap<Integer, Node> itemsByLineNumber = new TreeMap<>();
200 
201         List<ASTClassOrInterfaceDeclaration> packageDecl = cUnit
202                 .findDescendantsOfType(ASTClassOrInterfaceDeclaration.class);
203         addDeclarations(itemsByLineNumber, packageDecl);
204 
205         addDeclarations(itemsByLineNumber, cUnit.getComments());
206 
207         List<ASTFieldDeclaration> fields = cUnit.findDescendantsOfType(ASTFieldDeclaration.class);
208         addDeclarations(itemsByLineNumber, fields);
209 
210         List<ASTMethodDeclaration> methods = cUnit.findDescendantsOfType(ASTMethodDeclaration.class);
211         addDeclarations(itemsByLineNumber, methods);
212 
213         List<ASTConstructorDeclaration> constructors = cUnit.findDescendantsOfType(ASTConstructorDeclaration.class);
214         addDeclarations(itemsByLineNumber, constructors);
215 
216         List<ASTEnumDeclaration> enumDecl = cUnit.findDescendantsOfType(ASTEnumDeclaration.class);
217         addDeclarations(itemsByLineNumber, enumDecl);
218 
219         return itemsByLineNumber;
220     }
221 
222     private void addDeclarations(SortedMap<Integer, Node> map, List<? extends Node> nodes) {
223         for (Node node : nodes) {
224             map.put((node.getBeginLine() << 16) + node.getBeginColumn(), node);
225         }
226     }
227 }