blob: 30d895eafd88dc21f5cc1d6ebb4771e4b0a30107 [file] [log] [blame]
Pavlin Radoslavovd26f57a2014-10-23 17:19:45 -07001package org.onlab.junit;
Brian O'Connorf3d06162014-10-02 15:54:12 -07002
3import org.hamcrest.Description;
4import org.hamcrest.StringDescription;
5
6import java.lang.reflect.Field;
7import java.lang.reflect.Method;
8import java.lang.reflect.Modifier;
9
10/**
11 * Hamcrest style class for verifying that a class follows the
12 * accepted rules for immutable classes.
13 *
14 * The rules that are enforced for immutable classes:
15 * - the class must be declared final
16 * - all data members of the class must be declared private and final
17 * - the class must not define any setter methods
18 */
19
20public class ImmutableClassChecker {
21
22 private String failureReason = "";
23
24 /**
25 * Method to determine if a given class is a properly specified
26 * immutable class.
27 *
28 * @param clazz the class to check
29 * @return true if the given class is a properly specified immutable class.
30 */
31 private boolean isImmutableClass(Class<?> clazz) {
32 // class must be declared final
33 if (!Modifier.isFinal(clazz.getModifiers())) {
34 failureReason = "a class that is not final";
35 return false;
36 }
37
38 // class must have only final and private data members
39 for (final Field field : clazz.getDeclaredFields()) {
40 if (field.getName().startsWith("__cobertura")) {
41 // cobertura sticks these fields into classes - ignore them
42 continue;
43 }
44 if (!Modifier.isFinal(field.getModifiers())) {
45 failureReason = "a field named '" + field.getName() +
46 "' that is not final";
47 return false;
48 }
49 if (!Modifier.isPrivate(field.getModifiers())) {
50 //
51 // NOTE: We relax the recommended rules for defining immutable
52 // objects and allow "static final" fields that are not
53 // private. The "final" check was already done above so we
54 // don't repeat it here.
55 //
56 if (!Modifier.isStatic(field.getModifiers())) {
57 failureReason = "a field named '" + field.getName() +
58 "' that is not private and is not static";
59 return false;
60 }
61 }
62 }
63
64 // class must not define any setters
65 for (final Method method : clazz.getMethods()) {
66 if (method.getDeclaringClass().equals(clazz)) {
67 if (method.getName().startsWith("set")) {
68 failureReason = "a class with a setter named '" + method.getName() + "'";
69 return false;
70 }
71 }
72 }
73
74 return true;
75 }
76
77 /**
78 * Describe why an error was reported. Uses Hamcrest style Description
79 * interfaces.
80 *
81 * @param description the Description object to use for reporting the
82 * mismatch
83 */
84 public void describeMismatch(Description description) {
85 description.appendText(failureReason);
86 }
87
88 /**
89 * Describe the source object that caused an error, using a Hamcrest
90 * Matcher style interface. In this case, it always returns
91 * that we are looking for a properly defined utility class.
92 *
93 * @param description the Description object to use to report the "to"
94 * object
95 */
96 public void describeTo(Description description) {
97 description.appendText("a properly defined immutable class");
98 }
99
100 /**
101 * Assert that the given class adheres to the utility class rules.
102 *
103 * @param clazz the class to check
104 *
105 * @throws java.lang.AssertionError if the class is not a valid
106 * utility class
107 */
108 public static void assertThatClassIsImmutable(Class<?> clazz) {
109 final ImmutableClassChecker checker = new ImmutableClassChecker();
110 if (!checker.isImmutableClass(clazz)) {
111 final Description toDescription = new StringDescription();
112 final Description mismatchDescription = new StringDescription();
113
114 checker.describeTo(toDescription);
115 checker.describeMismatch(mismatchDescription);
116 final String reason =
117 "\n" +
118 "Expected: is \"" + toDescription.toString() + "\"\n" +
119 " but : was \"" + mismatchDescription.toString() + "\"";
120
121 throw new AssertionError(reason);
122 }
123 }
124}