blob: ac7e099f011c296f8e93d180749479aee2cb697e [file] [log] [blame]
Ray Milkey27bd3352014-05-14 16:59:25 -07001package net.onrc.onos.core.util;
2
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 */
Ray Milkeye3dadc32014-05-19 15:27:42 -070031 private boolean isImmutableClass(Class<?> clazz) {
Ray Milkey27bd3352014-05-14 16:59:25 -070032 // 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()) {
Ray Milkey4567e4c2014-05-15 19:28:24 -070040 if (field.getName().startsWith("__cobertura")) {
41 // cobertura sticks these fields into classes - ignore them
42 continue;
43 }
Ray Milkey27bd3352014-05-14 16:59:25 -070044 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 failureReason = "a field named '" + field.getName() +
51 "' that is not private";
52 return false;
53 }
54 }
55
56 // class must not define any setters
57 for (final Method method : clazz.getMethods()) {
58 if (method.getDeclaringClass().equals(clazz)) {
59 if (method.getName().startsWith("set")) {
60 failureReason = "a class with a setter named '" + method.getName() + "'";
61 return false;
62 }
63 }
64 }
65
66 return true;
67 }
68
69 /**
70 * Describe why an error was reported. Uses Hamcrest style Description
71 * interfaces.
72 *
73 * @param description the Description object to use for reporting the
74 * mismatch
75 */
76 public void describeMismatch(Description description) {
77 description.appendText(failureReason);
78 }
79
80 /**
81 * Describe the source object that caused an error, using a Hamcrest
82 * Matcher style interface. In this case, it always returns
83 * that we are looking for a properly defined utility class.
84 *
85 * @param description the Description object to use to report the "to"
86 * object
87 */
88 public void describeTo(Description description) {
89 description.appendText("a properly defined immutable class");
90 }
91
92 /**
93 * Assert that the given class adheres to the utility class rules.
94 *
95 * @param clazz the class to check
96 *
97 * @throws java.lang.AssertionError if the class is not a valid
98 * utility class
99 */
Ray Milkeye3dadc32014-05-19 15:27:42 -0700100 public static void assertThatClassIsImmutable(Class<?> clazz) {
Ray Milkey27bd3352014-05-14 16:59:25 -0700101 final ImmutableClassChecker checker = new ImmutableClassChecker();
102 if (!checker.isImmutableClass(clazz)) {
103 final Description toDescription = new StringDescription();
104 final Description mismatchDescription = new StringDescription();
105
106 checker.describeTo(toDescription);
107 checker.describeMismatch(mismatchDescription);
108 final String reason =
109 "\n" +
110 "Expected: is \"" + toDescription.toString() + "\"\n" +
111 " but : was \"" + mismatchDescription.toString() + "\"";
112
113 throw new AssertionError(reason);
114 }
115 }
116}