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