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