UtilityClassChecker.java 5.22 KB
/*
 * 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 org.onlab.junit.TestUtils.TestUtilsException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;


/**
 * Hamcrest style class for verifying that a class follows the
 * accepted rules for utility classes.
 *
 * The rules that are enforced for utility classes:
 *    - the class must be declared final
 *    - the class must have only one constructor
 *    - the constructor must be private and inaccessible to callers
 *    - the class must have only static methods
 */

public class UtilityClassChecker {

    private String failureReason = "";

    /**
     * Method to determine if a given class is a properly specified
     * utility class.  In addition to checking that the class meets the criteria
     * for utility classes, an object of the class type is allocated to force
     * test code coverage onto the class constructor.
     *
     * @param clazz the class to check
     * @return true if the given class is a properly specified utility class.
     */
    private boolean isProperlyDefinedUtilityClass(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 one constructor
        final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        if (constructors.length != 1) {
            failureReason = "a class with more than one constructor";
            return false;
        }

        //  constructor must not be accessible outside of the class
        final Constructor<?> constructor = constructors[0];
        if (constructor.isAccessible()) {
            failureReason = "a class with an accessible default constructor";
            return false;
        }

        // constructor must be private
        if (!Modifier.isPrivate(constructor.getModifiers())) {
            failureReason = "a class with a default constructor that is not private";
            return false;
        }

        // class must have only static methods
        for (final Method method : clazz.getMethods()) {
            if (method.getDeclaringClass().equals(clazz)) {
                if (!Modifier.isStatic(method.getModifiers())) {
                    failureReason = "a class with one or more non-static methods";
                    return false;
                }
            }

        }

        try {
            final Object newObject = TestUtils.callConstructor(constructor);
            if (newObject == null) {
                failureReason = "could not instantiate a new object";
                return false;
            }
        } catch (TestUtilsException e) {
            failureReason = "could not instantiate a new object";
            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 utility 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 assertThatClassIsUtility(Class<?> clazz) {
        final UtilityClassChecker checker = new UtilityClassChecker();
        if (!checker.isProperlyDefinedUtilityClass(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);
        }
    }
}