1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package com.jcabi.aspects.aj;
31
32 import com.jcabi.aspects.Immutable;
33 import com.jcabi.aspects.version.Version;
34 import com.jcabi.log.Logger;
35 import java.lang.reflect.Field;
36 import java.lang.reflect.Modifier;
37 import java.util.Collection;
38 import java.util.HashSet;
39 import org.aspectj.lang.JoinPoint;
40 import org.aspectj.lang.annotation.After;
41 import org.aspectj.lang.annotation.Aspect;
42
43
44
45
46
47
48
49
50 @Aspect
51 public final class ImmutabilityChecker {
52
53
54
55
56 private final transient Collection<Class<?>> immutable = new HashSet<>();
57
58
59
60
61
62
63
64
65 @After("initialization((@com.jcabi.aspects.Immutable *).new(..))")
66 public void after(final JoinPoint point) {
67 final Class<?> type = point.getTarget().getClass();
68 try {
69 this.check(type);
70 } catch (final ImmutabilityChecker.Violation ex) {
71 throw new IllegalStateException(
72 String.format(
73
74 "%s is not immutable, can't use it (jcabi-aspects %s/%s)",
75 type,
76 Version.CURRENT.projectVersion(),
77 Version.CURRENT.buildNumber()
78 ),
79 ex
80 );
81 }
82 }
83
84
85
86
87
88
89 private void check(final Class<?> type)
90 throws ImmutabilityChecker.Violation {
91 synchronized (this.immutable) {
92 if (!this.ignore(type)) {
93 if (type.isInterface()
94 && !type.isAnnotationPresent(Immutable.class)) {
95 throw new ImmutabilityChecker.Violation(
96 String.format(
97 "Interface '%s' is not annotated with @Immutable",
98 type.getName()
99 )
100 );
101 }
102 if (!type.isInterface()
103 && !Modifier.isFinal(type.getModifiers())) {
104 throw new ImmutabilityChecker.Violation(
105 String.format(
106 "Class '%s' is not final",
107 type.getName()
108 )
109 );
110 }
111 try {
112 this.fields(type);
113 } catch (final ImmutabilityChecker.Violation ex) {
114 throw new ImmutabilityChecker.Violation(
115 String.format("Class '%s' is mutable", type.getName()),
116 ex
117 );
118 }
119 this.immutable.add(type);
120 Logger.debug(this, "#check(%s): immutability checked", type);
121 }
122 }
123 }
124
125
126
127
128
129
130 private boolean ignore(final Class<?> type) {
131
132 return type.equals(Object.class)
133 || type.equals(String.class)
134 || type.isPrimitive()
135 || type.getName().startsWith("org.aspectj.runtime.reflect.")
136 || this.immutable.contains(type);
137 }
138
139
140
141
142
143
144 private void fields(final Class<?> type)
145 throws ImmutabilityChecker.Violation {
146 final Field[] fields = type.getDeclaredFields();
147 for (final Field field : fields) {
148 if (Modifier.isStatic(field.getModifiers())) {
149 continue;
150 }
151 if (!Modifier.isFinal(field.getModifiers())) {
152 throw new ImmutabilityChecker.Violation(
153 String.format(
154 "field '%s' is not final in %s",
155 field, type.getName()
156 )
157 );
158 }
159 try {
160 if (field.getType().isArray()) {
161 this.checkArray(field);
162 }
163 } catch (final ImmutabilityChecker.Violation ex) {
164 throw new ImmutabilityChecker.Violation(
165 String.format(
166 "field '%s' is mutable",
167 field
168 ),
169 ex
170 );
171 }
172 }
173 }
174
175
176
177
178
179
180 private void checkArray(final Field field)
181 throws ImmutabilityChecker.Violation {
182 if (!field.isAnnotationPresent(Immutable.Array.class)) {
183 throw new ImmutabilityChecker.Violation(
184 String.format(
185
186 "Field '%s' is an array and is not annotated with @Immutable.Array",
187 field.getName()
188 )
189 );
190 }
191 final Class<?> type = field.getType().getComponentType();
192 try {
193 this.check(type);
194 } catch (final ImmutabilityChecker.Violation ex) {
195 throw new ImmutabilityChecker.Violation(
196 String.format(
197 "Field array component type '%s' is mutable",
198 type.getName()
199 ),
200 ex
201 );
202 }
203 }
204
205
206
207
208
209 private static final class Violation extends Exception {
210
211
212
213
214 private static final long serialVersionUID = 1L;
215
216
217
218
219
220 private Violation(final String msg) {
221 super(msg);
222 }
223
224
225
226
227
228
229 private Violation(final String msg, final Exception cause) {
230 super(msg, cause);
231 }
232 }
233
234 }