blob: 2ac20b7bda210a059119f260abd28a7cdbbe6868 [file] [log] [blame]
Thomas Vachuska24c849c2014-10-27 09:53:05 -07001/*
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07002 * Copyright 2014 Open Networking Laboratory
Thomas Vachuska24c849c2014-10-27 09:53:05 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Thomas Vachuska24c849c2014-10-27 09:53:05 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Thomas Vachuska24c849c2014-10-27 09:53:05 -070015 */
Pavlin Radoslavovd26f57a2014-10-23 17:19:45 -070016package org.onlab.junit;
Brian O'Connorf3d06162014-10-02 15:54:12 -070017
18import org.hamcrest.Description;
19import org.hamcrest.StringDescription;
20
21import java.lang.reflect.Field;
22import java.lang.reflect.Method;
23import java.lang.reflect.Modifier;
24
25/**
26 * Hamcrest style class for verifying that a class follows the
27 * accepted rules for immutable classes.
28 *
29 * The rules that are enforced for immutable classes:
30 * - the class must be declared final
31 * - all data members of the class must be declared private and final
32 * - the class must not define any setter methods
33 */
34
35public class ImmutableClassChecker {
36
37 private String failureReason = "";
38
39 /**
40 * Method to determine if a given class is a properly specified
41 * immutable class.
42 *
43 * @param clazz the class to check
44 * @return true if the given class is a properly specified immutable class.
45 */
46 private boolean isImmutableClass(Class<?> clazz) {
47 // class must be declared final
48 if (!Modifier.isFinal(clazz.getModifiers())) {
49 failureReason = "a class that is not final";
50 return false;
51 }
52
53 // class must have only final and private data members
54 for (final Field field : clazz.getDeclaredFields()) {
55 if (field.getName().startsWith("__cobertura")) {
56 // cobertura sticks these fields into classes - ignore them
57 continue;
58 }
59 if (!Modifier.isFinal(field.getModifiers())) {
60 failureReason = "a field named '" + field.getName() +
61 "' that is not final";
62 return false;
63 }
64 if (!Modifier.isPrivate(field.getModifiers())) {
65 //
66 // NOTE: We relax the recommended rules for defining immutable
67 // objects and allow "static final" fields that are not
68 // private. The "final" check was already done above so we
69 // don't repeat it here.
70 //
71 if (!Modifier.isStatic(field.getModifiers())) {
72 failureReason = "a field named '" + field.getName() +
73 "' that is not private and is not static";
74 return false;
75 }
76 }
77 }
78
79 // class must not define any setters
80 for (final Method method : clazz.getMethods()) {
81 if (method.getDeclaringClass().equals(clazz)) {
82 if (method.getName().startsWith("set")) {
83 failureReason = "a class with a setter named '" + method.getName() + "'";
84 return false;
85 }
86 }
87 }
88
89 return true;
90 }
91
92 /**
93 * Describe why an error was reported. Uses Hamcrest style Description
94 * interfaces.
95 *
96 * @param description the Description object to use for reporting the
97 * mismatch
98 */
99 public void describeMismatch(Description description) {
100 description.appendText(failureReason);
101 }
102
103 /**
104 * Describe the source object that caused an error, using a Hamcrest
105 * Matcher style interface. In this case, it always returns
106 * that we are looking for a properly defined utility class.
107 *
108 * @param description the Description object to use to report the "to"
109 * object
110 */
111 public void describeTo(Description description) {
112 description.appendText("a properly defined immutable class");
113 }
114
115 /**
116 * Assert that the given class adheres to the utility class rules.
117 *
118 * @param clazz the class to check
119 *
120 * @throws java.lang.AssertionError if the class is not a valid
121 * utility class
122 */
123 public static void assertThatClassIsImmutable(Class<?> clazz) {
124 final ImmutableClassChecker checker = new ImmutableClassChecker();
125 if (!checker.isImmutableClass(clazz)) {
126 final Description toDescription = new StringDescription();
127 final Description mismatchDescription = new StringDescription();
128
129 checker.describeTo(toDescription);
130 checker.describeMismatch(mismatchDescription);
131 final String reason =
132 "\n" +
133 "Expected: is \"" + toDescription.toString() + "\"\n" +
134 " but : was \"" + mismatchDescription.toString() + "\"";
135
136 throw new AssertionError(reason);
137 }
138 }
139}