blob: f2dbb3f99daf9af405187db1c3e4b6bcc4056fb3 [file] [log] [blame]
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.junit;
import org.hamcrest.Description;
import org.hamcrest.StringDescription;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Hamcrest style class for verifying that a class follows the
* accepted rules for immutable classes.
*
* The rules that are enforced for immutable classes:
* - the class must be declared final
* - all data members of the class must be declared private and final
* - the class must not define any setter methods
*/
public class ImmutableClassChecker {
private String failureReason = "";
/**
* Method to determine if a given class is a properly specified
* immutable class.
*
* @param clazz the class to check
* @return true if the given class is a properly specified immutable class.
*/
private boolean isImmutableClass(Class<?> clazz, boolean allowNonFinalClass) {
// class must be declared final
if (!allowNonFinalClass && !Modifier.isFinal(clazz.getModifiers())) {
failureReason = "a class that is not final";
return false;
}
// class must have only final and private data members
for (final Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith("_") ||
field.getName().startsWith("$")) {
// eclipse generated code may insert switch table - ignore
// cobertura sticks these fields into classes - ignore them
continue;
}
if (!Modifier.isFinal(field.getModifiers())) {
failureReason = "a field named '" + field.getName() +
"' that is not final";
return false;
}
if (!Modifier.isPrivate(field.getModifiers())) {
//
// NOTE: We relax the recommended rules for defining immutable
// objects and allow "static final" fields that are not
// private. The "final" check was already done above so we
// don't repeat it here.
//
if (!Modifier.isStatic(field.getModifiers())) {
failureReason = "a field named '" + field.getName() +
"' that is not private and is not static";
return false;
}
}
}
// class must not define any setters
for (final Method method : clazz.getMethods()) {
if (method.getDeclaringClass().equals(clazz)) {
if (method.getName().startsWith("set")) {
failureReason = "a class with a setter named '" + method.getName() + "'";
return false;
}
}
}
return true;
}
/**
* Describe why an error was reported. Uses Hamcrest style Description
* interfaces.
*
* @param description the Description object to use for reporting the
* mismatch
*/
public void describeMismatch(Description description) {
description.appendText(failureReason);
}
/**
* Describe the source object that caused an error, using a Hamcrest
* Matcher style interface. In this case, it always returns
* that we are looking for a properly defined utility class.
*
* @param description the Description object to use to report the "to"
* object
*/
public void describeTo(Description description) {
description.appendText("a properly defined immutable class");
}
/**
* Assert that the given class adheres to the immutable class rules.
*
* @param clazz the class to check
*
* @throws java.lang.AssertionError if the class is not an
* immutable class
*/
public static void assertThatClassIsImmutable(Class<?> clazz) {
final ImmutableClassChecker checker = new ImmutableClassChecker();
if (!checker.isImmutableClass(clazz, false)) {
final Description toDescription = new StringDescription();
final Description mismatchDescription = new StringDescription();
checker.describeTo(toDescription);
checker.describeMismatch(mismatchDescription);
final String reason =
"\n" +
"Expected: is \"" + toDescription.toString() + "\"\n" +
" but : was \"" + mismatchDescription.toString() + "\"";
throw new AssertionError(reason);
}
}
/**
* Assert that the given class adheres to the immutable class rules, but
* is not declared final. Classes that need to be inherited from cannot be
* declared final.
*
* @param clazz the class to check
*
* @throws java.lang.AssertionError if the class is not an
* immutable class
*/
public static void assertThatClassIsImmutableBaseClass(Class<?> clazz) {
final ImmutableClassChecker checker = new ImmutableClassChecker();
if (!checker.isImmutableClass(clazz, true)) {
final Description toDescription = new StringDescription();
final Description mismatchDescription = new StringDescription();
checker.describeTo(toDescription);
checker.describeMismatch(mismatchDescription);
final String reason =
"\n" +
"Expected: is \"" + toDescription.toString() + "\"\n" +
" but : was \"" + mismatchDescription.toString() + "\"";
throw new AssertionError(reason);
}
}
}