ImmutableClassChecker.java 4.37 KB
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) {
        // class must be declared final
        if (!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("__cobertura")) {
                //  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 utility class rules.
     *
     * @param clazz the class to check
     *
     * @throws java.lang.AssertionError if the class is not a valid
     *         utility class
     */
    public static void assertThatClassIsImmutable(Class<?> clazz) {
        final ImmutableClassChecker checker = new ImmutableClassChecker();
        if (!checker.isImmutableClass(clazz)) {
            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);
        }
    }
}