1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package net.sf.clirr.core.internal.checks;
21
22 import net.sf.clirr.core.Severity;
23 import net.sf.clirr.core.Message;
24 import net.sf.clirr.core.internal.AbstractDiffReporter;
25 import net.sf.clirr.core.internal.ApiDiffDispatcher;
26 import net.sf.clirr.core.internal.ClassChangeCheck;
27 import net.sf.clirr.core.spi.JavaType;
28 import net.sf.clirr.core.spi.Method;
29 import net.sf.clirr.core.spi.Scope;
30
31 /***
32 * Detects changes in class modifiers (abstract, final).
33 *
34 * @author lkuehne
35 */
36 public final class ClassModifierCheck
37 extends AbstractDiffReporter
38 implements ClassChangeCheck
39 {
40 private static final Message MSG_MODIFIER_UNABLE_TO_DETERMINE_CLASS_SCOPE = new Message(3000);
41 private static final Message MSG_MODIFIER_REMOVED_FINAL = new Message(3001);
42 private static final Message MSG_MODIFIER_ADDED_FINAL_TO_EFFECTIVE_FINAL = new Message(3002);
43 private static final Message MSG_MODIFIER_ADDED_FINAL = new Message(3003);
44 private static final Message MSG_MODIFIER_REMOVED_ABSTRACT = new Message(3004);
45 private static final Message MSG_MODIFIER_ADDED_ABSTRACT = new Message(3005);
46
47 /***
48 * Create a new instance of this check.
49 * @param dispatcher the diff dispatcher that distributes the detected changes to the listeners.
50 */
51 public ClassModifierCheck(ApiDiffDispatcher dispatcher)
52 {
53 super(dispatcher);
54 }
55
56 /*** {@inheritDoc} */
57 public boolean check(JavaType compatBaseLine, JavaType currentVersion)
58 {
59 final String className = compatBaseLine.getName();
60
61 Scope currentScope = currentVersion.getEffectiveScope();
62 if (currentScope.isLessVisibleThan(Scope.PACKAGE))
63 {
64
65
66 return true;
67 }
68
69 final boolean currentIsFinal = currentVersion.isFinal();
70 final boolean compatIsFinal = compatBaseLine.isFinal();
71 final boolean currentIsAbstract = currentVersion.isAbstract();
72 final boolean compatIsAbstract = compatBaseLine.isAbstract();
73 final boolean currentIsInterface = currentVersion.isInterface();
74 final boolean compatIsInterface = compatBaseLine.isInterface();
75
76 if (compatIsFinal && !currentIsFinal)
77 {
78 log(MSG_MODIFIER_REMOVED_FINAL,
79 Severity.INFO, className, null, null, null);
80 }
81 else if (!compatIsFinal && currentIsFinal)
82 {
83 if (isEffectivelyFinal(compatBaseLine))
84 {
85 log(MSG_MODIFIER_ADDED_FINAL_TO_EFFECTIVE_FINAL,
86 Severity.INFO, className, null, null, null);
87 }
88 else
89 {
90 log(MSG_MODIFIER_ADDED_FINAL,
91 getSeverity(compatBaseLine, Severity.ERROR),
92 className, null, null, null);
93 }
94 }
95
96
97 if (compatIsAbstract && !currentIsAbstract && !compatIsInterface)
98 {
99 log(MSG_MODIFIER_REMOVED_ABSTRACT,
100 Severity.INFO, className, null, null, null);
101 }
102 else if (!compatIsAbstract && currentIsAbstract && !currentIsInterface)
103 {
104 log(MSG_MODIFIER_ADDED_ABSTRACT,
105 getSeverity(compatBaseLine, Severity.ERROR),
106 className, null, null, null);
107 }
108
109 return true;
110 }
111
112 /***
113 * There are cases where nonfinal classes are effectively final
114 * because they do not have public or protected ctors. For such
115 * classes we should not emit errors when a final modifier is
116 * introduced.
117 */
118 private boolean isEffectivelyFinal(JavaType clazz)
119 {
120 if (clazz.isFinal())
121 {
122 return true;
123 }
124
125
126
127 Method[] methods = clazz.getMethods();
128 for (int i = 0; i < methods.length; ++i)
129 {
130 Method method = methods[i];
131 final String methodName = method.getName();
132 if (methodName.equals("<init>"))
133 {
134 if (method.getEffectiveScope().isMoreVisibleThan(Scope.PACKAGE))
135 {
136 return false;
137 }
138 }
139 }
140
141
142 return true;
143 }
144 }