blob: f2dbb3f99daf9af405187db1c3e4b6bcc4056fb3 [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 */
Ray Milkey1e207112014-11-11 10:38:00 -080046 private boolean isImmutableClass(Class<?> clazz, boolean allowNonFinalClass) {
Brian O'Connorf3d06162014-10-02 15:54:12 -070047 // class must be declared final
Ray Milkey1e207112014-11-11 10:38:00 -080048 if (!allowNonFinalClass && !Modifier.isFinal(clazz.getModifiers())) {
Brian O'Connorf3d06162014-10-02 15:54:12 -070049 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()) {
Yuta HIGUCHI7887b472015-02-23 11:08:36 -080055 if (field.getName().startsWith("_") ||
56 field.getName().startsWith("$")) {
57 // eclipse generated code may insert switch table - ignore
Brian O'Connorf3d06162014-10-02 15:54:12 -070058 // cobertura sticks these fields into classes - ignore them
59 continue;
60 }
61 if (!Modifier.isFinal(field.getModifiers())) {
62 failureReason = "a field named '" + field.getName() +
63 "' that is not final";
64 return false;
65 }
66 if (!Modifier.isPrivate(field.getModifiers())) {
67 //
68 // NOTE: We relax the recommended rules for defining immutable
69 // objects and allow "static final" fields that are not
70 // private. The "final" check was already done above so we
71 // don't repeat it here.
72 //
73 if (!Modifier.isStatic(field.getModifiers())) {
74 failureReason = "a field named '" + field.getName() +
75 "' that is not private and is not static";
76 return false;
77 }
78 }
79 }
80
81 // class must not define any setters
82 for (final Method method : clazz.getMethods()) {
83 if (method.getDeclaringClass().equals(clazz)) {
84 if (method.getName().startsWith("set")) {
85 failureReason = "a class with a setter named '" + method.getName() + "'";
86 return false;
87 }
88 }
89 }
90
91 return true;
92 }
93
94 /**
95 * Describe why an error was reported. Uses Hamcrest style Description
96 * interfaces.
97 *
98 * @param description the Description object to use for reporting the
99 * mismatch
100 */
101 public void describeMismatch(Description description) {
102 description.appendText(failureReason);
103 }
104
105 /**
106 * Describe the source object that caused an error, using a Hamcrest
107 * Matcher style interface. In this case, it always returns
108 * that we are looking for a properly defined utility class.
109 *
110 * @param description the Description object to use to report the "to"
111 * object
112 */
113 public void describeTo(Description description) {
114 description.appendText("a properly defined immutable class");
115 }
116
117 /**
Ray Milkey1e207112014-11-11 10:38:00 -0800118 * Assert that the given class adheres to the immutable class rules.
Brian O'Connorf3d06162014-10-02 15:54:12 -0700119 *
120 * @param clazz the class to check
121 *
Ray Milkey1e207112014-11-11 10:38:00 -0800122 * @throws java.lang.AssertionError if the class is not an
123 * immutable class
Brian O'Connorf3d06162014-10-02 15:54:12 -0700124 */
125 public static void assertThatClassIsImmutable(Class<?> clazz) {
126 final ImmutableClassChecker checker = new ImmutableClassChecker();
Ray Milkey1e207112014-11-11 10:38:00 -0800127 if (!checker.isImmutableClass(clazz, false)) {
Brian O'Connorf3d06162014-10-02 15:54:12 -0700128 final Description toDescription = new StringDescription();
129 final Description mismatchDescription = new StringDescription();
130
131 checker.describeTo(toDescription);
132 checker.describeMismatch(mismatchDescription);
133 final String reason =
134 "\n" +
135 "Expected: is \"" + toDescription.toString() + "\"\n" +
136 " but : was \"" + mismatchDescription.toString() + "\"";
137
138 throw new AssertionError(reason);
139 }
140 }
Ray Milkey1e207112014-11-11 10:38:00 -0800141
142 /**
143 * Assert that the given class adheres to the immutable class rules, but
144 * is not declared final. Classes that need to be inherited from cannot be
145 * declared final.
146 *
147 * @param clazz the class to check
148 *
149 * @throws java.lang.AssertionError if the class is not an
150 * immutable class
151 */
152 public static void assertThatClassIsImmutableBaseClass(Class<?> clazz) {
153 final ImmutableClassChecker checker = new ImmutableClassChecker();
154 if (!checker.isImmutableClass(clazz, true)) {
155 final Description toDescription = new StringDescription();
156 final Description mismatchDescription = new StringDescription();
157
158 checker.describeTo(toDescription);
159 checker.describeMismatch(mismatchDescription);
160 final String reason =
161 "\n" +
162 "Expected: is \"" + toDescription.toString() + "\"\n" +
163 " but : was \"" + mismatchDescription.toString() + "\"";
164
165 throw new AssertionError(reason);
166 }
167 }
Brian O'Connorf3d06162014-10-02 15:54:12 -0700168}