1
2
3
4
5 package com.qulice.checkstyle;
6
7 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8 import com.puppycrawl.tools.checkstyle.api.DetailAST;
9 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10
11
12
13
14
15
16
17
18
19
20
21
22
23 public final class JavadocLocationCheck extends AbstractCheck {
24
25
26
27
28 public JavadocLocationCheck() {
29
30 }
31
32 @Override
33 public int[] getDefaultTokens() {
34 return new int[] {
35 TokenTypes.CLASS_DEF,
36 TokenTypes.INTERFACE_DEF,
37 TokenTypes.VARIABLE_DEF,
38 TokenTypes.CTOR_DEF,
39 TokenTypes.METHOD_DEF,
40 };
41 }
42
43 @Override
44 public int[] getAcceptableTokens() {
45 return this.getDefaultTokens();
46 }
47
48 @Override
49 public int[] getRequiredTokens() {
50 return this.getDefaultTokens();
51 }
52
53 @Override
54 public void visitToken(final DetailAST ast) {
55 if (!JavadocLocationCheck.isField(ast)) {
56 return;
57 }
58 final String[] lines = this.getLines();
59 this.checkEmptyLines(ast, lines);
60 this.checkAnnotationAboveJavadoc(ast, lines);
61 }
62
63 private void checkEmptyLines(final DetailAST ast, final String... lines) {
64 final int current = JavadocLocationCheck.javadocEnd(
65 ast.getLineNo() - 1, lines
66 );
67 if (current > 0) {
68 final int diff = ast.getLineNo() - current;
69 for (int pos = 1; pos < diff; pos += 1) {
70 this.log(
71 current + pos,
72 "Empty line between javadoc and subject"
73 );
74 }
75 }
76 }
77
78 private static int javadocEnd(final int from, final String... lines) {
79 int current = from;
80 int result = 0;
81 while (current > 0) {
82 final String line = lines[current - 1].trim();
83 if (line.endsWith("*/")) {
84 result = current;
85 break;
86 }
87 if (!line.isEmpty()) {
88 break;
89 }
90 current -= 1;
91 }
92 return result;
93 }
94
95 private void checkAnnotationAboveJavadoc(
96 final DetailAST ast, final String... lines
97 ) {
98 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
99 if (modifiers != null) {
100 final DetailAST after = modifiers.getNextSibling();
101 final int annotation = JavadocLocationCheck.firstAnnotationLine(
102 modifiers
103 );
104 if (after != null && annotation != Integer.MAX_VALUE
105 && JavadocLocationCheck.javadocBetween(
106 annotation, after.getLineNo(), lines
107 )) {
108 this.log(annotation, "Annotation must be placed after Javadoc");
109 }
110 }
111 }
112
113 private static int firstAnnotationLine(final DetailAST modifiers) {
114 int line = Integer.MAX_VALUE;
115 DetailAST child = modifiers.getFirstChild();
116 while (child != null) {
117 if (child.getType() == TokenTypes.ANNOTATION
118 && child.getLineNo() < line) {
119 line = child.getLineNo();
120 }
121 child = child.getNextSibling();
122 }
123 return line;
124 }
125
126 private static boolean javadocBetween(final int start, final int end,
127 final String... lines) {
128 boolean found = false;
129 for (int pos = start + 1; pos < end; pos += 1) {
130 final String line = lines[pos - 1].trim();
131 if (line.startsWith("/**") || line.endsWith("*/")) {
132 found = true;
133 break;
134 }
135 }
136 return found;
137 }
138
139 private static boolean isField(final DetailAST node) {
140 boolean yes = true;
141 if (TokenTypes.VARIABLE_DEF == node.getType()) {
142 yes = TokenTypes.OBJBLOCK == node.getParent().getType();
143 }
144 return yes;
145 }
146 }