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