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 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
11 import java.util.ArrayDeque;
12 import java.util.Deque;
13 import org.cactoos.text.Joined;
14 import org.cactoos.text.UncheckedText;
15
16
17
18
19
20
21
22
23
24
25
26
27 public final class ProhibitNonFinalClassesCheck extends AbstractCheck {
28
29
30
31
32 private static final String PACKAGE_SEPARATOR = ".";
33
34
35
36
37 private Deque<ClassDesc> classes;
38
39
40
41
42 private String pack;
43
44
45
46
47 public ProhibitNonFinalClassesCheck() {
48 this.classes = new ArrayDeque<>();
49 }
50
51 @Override
52 public int[] getDefaultTokens() {
53 return this.getRequiredTokens();
54 }
55
56 @Override
57 public int[] getAcceptableTokens() {
58 return this.getRequiredTokens();
59 }
60
61 @Override
62 public int[] getRequiredTokens() {
63 return new int[] {TokenTypes.CLASS_DEF};
64 }
65
66 @Override
67 public void beginTree(final DetailAST root) {
68 this.classes = new ArrayDeque<>();
69 this.pack = "";
70 }
71
72 @Override
73 public void visitToken(final DetailAST ast) {
74 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
75 if (ast.getType() == TokenTypes.CLASS_DEF) {
76 this.classes.push(
77 new ClassDesc(
78 this.qualifiedClassName(ast),
79 modifiers.findFirstToken(TokenTypes.FINAL) != null,
80 modifiers.findFirstToken(TokenTypes.ABSTRACT) != null
81 )
82 );
83 }
84 }
85
86 @Override
87 public void leaveToken(final DetailAST ast) {
88 if (ast.getType() == TokenTypes.CLASS_DEF) {
89 final ClassDesc desc = this.classes.pop();
90 if (!desc.isDeclaredAsAbstract()
91 && !desc.isAsfinal()
92 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
93 log(
94 ast.getLineNo(),
95 "Classes should be final",
96 ProhibitNonFinalClassesCheck.getClassNameFromQualifiedName(
97 desc.getQualified()
98 )
99 );
100 }
101 }
102 }
103
104 private String qualifiedClassName(final DetailAST classast) {
105 String outer = null;
106 if (!this.classes.isEmpty()) {
107 outer = this.classes.peek().getQualified();
108 }
109 return ProhibitNonFinalClassesCheck.getQualifiedClassName(
110 this.pack,
111 outer,
112 classast.findFirstToken(TokenTypes.IDENT).getText()
113 );
114 }
115
116 private static String getQualifiedClassName(
117 final String pack,
118 final String outer,
119 final String name) {
120 final String qualified;
121 if (outer == null) {
122 if (pack.isEmpty()) {
123 qualified = name;
124 } else {
125 qualified =
126 new UncheckedText(
127 new Joined(
128 ProhibitNonFinalClassesCheck.PACKAGE_SEPARATOR,
129 pack,
130 name
131 )
132 ).asString();
133 }
134 } else {
135 qualified =
136 new UncheckedText(
137 new Joined(
138 ProhibitNonFinalClassesCheck.PACKAGE_SEPARATOR,
139 outer,
140 name
141 )
142 ).asString();
143 }
144 return qualified;
145 }
146
147 private static String getClassNameFromQualifiedName(
148 final String qualified
149 ) {
150 return qualified.substring(
151 qualified.lastIndexOf(
152 ProhibitNonFinalClassesCheck.PACKAGE_SEPARATOR
153 ) + 1
154 );
155 }
156 }