blob: d3fcc657e6cd59c4e2244a3013b99f1a02d902fe [file] [log] [blame]
Ray Milkey22553b92014-05-06 11:06:21 -07001package net.onrc.onos.core.util;
2
3import org.hamcrest.Description;
4import org.hamcrest.StringDescription;
5
6import java.lang.reflect.Constructor;
7import java.lang.reflect.InvocationTargetException;
8import java.lang.reflect.Method;
9import java.lang.reflect.Modifier;
10
11
12/**
13 * Hamcrest style class for verifying that a class follows the
14 * accepted rules for utility classes.
15 *
16 * The rules that are enforced for utility classes:
17 * - the class must be declared final
18 * - the class must have only one constructor
19 * - the constructor must be private and inaccessible to callers
20 * - the class must have only static methods
21 */
22
23public class UtilityClassChecker {
24
25 private String failureReason = "";
26
27 /**
28 * Method to determine if a given class is a properly specified
29 * utility class. In addition to checking that the class meets the criteria
30 * for utility classes, an object of the class type is allocated to force
31 * test code coverage onto the class constructor.
32 *
33 * @param clazz the class to check
34 * @return true if the given class is a properly specified utility class.
35 */
36 private boolean isProperlyDefinedUtilityClass(Class clazz) {
37 // class must be declared final
38 if (!Modifier.isFinal(clazz.getModifiers())) {
39 failureReason = "a class that is not final";
40 return false;
41 }
42
43 // class must have only one constructor
44 final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
45 if (constructors.length != 1) {
46 failureReason = "a class with more than one constructor";
47 return false;
48 }
49
50 // constructor must not be accessible outside of the class
51 final Constructor<?> constructor = constructors[0];
52 if (constructor.isAccessible()) {
53 failureReason = "a class with an accessible default constructor";
54 return false;
55 }
56
57 // constructor must be private
58 if (!Modifier.isPrivate(constructor.getModifiers())) {
59 failureReason = "a class with a default constructor that is not private";
60 return false;
61 }
62
63 // class must have only static methods
64 for (final Method method : clazz.getMethods()) {
65 if (method.getDeclaringClass().equals(clazz)) {
66 if (!Modifier.isStatic(method.getModifiers())) {
67 failureReason = "a class with one or more non-static methods";
68 return false;
69 }
70 }
71
72 }
73
74 // Trigger an allocation of the object to force the hidden
75 // constructor call
76 try {
77 constructor.setAccessible(true);
78 constructor.newInstance();
79 constructor.setAccessible(false);
80 } catch (InstantiationException|IllegalAccessException|
81 InvocationTargetException error) {
82 failureReason = error.getCause() + " when calling constructor";
83 return false;
84 }
85 return true;
86 }
87
88 /**
89 * Describe why an error was reported. Uses Hamcrest style Description
90 * interfaces.
91 *
92 * @param description the Description object to use for reporting the
93 * mismatch
94 */
95 public void describeMismatch(Description description) {
96 description.appendText(failureReason);
97 }
98
99 /**
100 * Describe the source object that caused an error, using a Hamcrest
101 * Matcher style interface. In this case, it always returns
102 * that we are looking for a properly defined utility class.
103 *
104 * @param description the Description object to use to report the "to"
105 * object
106 */
107 public void describeTo(Description description) {
108 description.appendText("a properly defined utility class");
109 }
110
111 /**
112 * Assert that the given class adheres to the utility class rules.
113 *
114 * @param clazz the class to check
115 *
116 * @throws java.lang.AssertionError if the class is not a valid
117 * utility class
118 */
119 public static void assertThatClassIsUtility(Class clazz) {
120 final UtilityClassChecker checker = new UtilityClassChecker();
121 if (!checker.isProperlyDefinedUtilityClass(clazz)) {
122 final Description toDescription = new StringDescription();
123 final Description mismatchDescription = new StringDescription();
124
125 checker.describeTo(toDescription);
126 checker.describeMismatch(mismatchDescription);
127 final String reason =
128 "\n" +
129 "Expected: is \"" + toDescription.toString() + "\"\n" +
130 " but : was \"" + mismatchDescription.toString() + "\"";
131
132 throw new AssertionError(reason);
133 }
134 }
135}