com.google.common.truth.Subject.java Source code

Java tutorial

Introduction

Here is the source code for com.google.common.truth.Subject.java

Source

/*
 * Copyright (c) 2011 Google, Inc.
 *
 * 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 com.google.common.truth;

import static com.google.common.truth.StringUtil.format;

import com.google.gwt.core.shared.GwtIncompatible;

import java.lang.reflect.Field;

/**
 * Propositions for arbitrarily typed subjects and for properties
 * of Object
 *
 * @author David Saff
 * @author Christian Gruber (cgruber@israfil.net)
 */
public class Subject<S extends Subject<S, T>, T> {
    protected final FailureStrategy failureStrategy;
    private final T subject;
    private String customName = null;

    public Subject(FailureStrategy failureStrategy, T subject) {
        this.failureStrategy = failureStrategy;
        this.subject = subject;
    }

    protected String internalCustomName() {
        return customName;
    }

    /**
     * Renames the subject so that this name appears in the error messages in place of string
     * representations of the subject.
     */
    @SuppressWarnings("unchecked")
    public S named(String name) {
        if (name == null) {
            // TODO: use check().withFailureMessage... here?
            throw new NullPointerException("Name passed to named() cannot be null.");
        }
        this.customName = name;
        return (S) this;
    }

    /**
     * Soft-deprecated in favor of {@link #named(String)}.
     */
    public S labeled(String label) {
        return named(label);
    }

    public void is(Object other) {
        isEqualTo(other);
    }

    public void isNull() {
        if (getSubject() != null) {
            failWithoutSubject("is a null reference");
        }
    }

    public void isNotNull() {
        if (getSubject() == null) {
            failWithoutSubject("is a non-null reference");
        }
    }

    public void isEqualTo(Object other) {
        if (getSubject() == null) {
            if (other != null) {
                fail("is equal to", other);
            }
        } else {
            if (!getSubject().equals(other)) {
                fail("is equal to", other);
            }
        }
    }

    public void isNotEqualTo(Object other) {
        if (getSubject() == null) {
            if (other == null) {
                fail("is not equal to", (Object) null);
            }
        } else {
            if (getSubject().equals(other)) {
                fail("is not equal to", other);
            }
        }
    }

    public void isA(Class<?> clazz) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        }
        if (!Platform.isInstanceOfType(getSubject(), clazz)) {
            if (getSubject() != null) {
                failWithBadResults("is an instance of", clazz.getName(), "is an instance of",
                        getSubject().getClass().getName());
            } else {
                fail("is an instance of", clazz.getName());
            }
        }
    }

    public void isNotA(Class<?> clazz) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        }
        if (getSubject() == null) {
            return; // null is not an instance of clazz.
        }
        if (Platform.isInstanceOfType(getSubject(), clazz)) {
            failWithRawMessage("%s expected not to be an instance of %s, but was.", getDisplaySubject(),
                    clazz.getName());
        }
    }

    protected T getSubject() {
        return subject;
    }

    protected String getDisplaySubject() {
        return (customName == null) ? "<" + getSubject() + ">" : "\"" + this.customName + "\"";
    }

    /**
     * A convenience for implementers of {@link Subject} subclasses to use other truth
     * {@code Subject} wrappers within their own propositional logic.
     */
    protected TestVerb check() {
        return new TestVerb(failureStrategy);
    }

    /**
     * Assembles a failure message and passes such to the FailureStrategy
     *
     * @param verb the proposition being asserted
     * @param messageParts the expectations against which the subject is compared
     */
    protected void fail(String verb, Object... messageParts) {
        StringBuilder message = new StringBuilder("Not true that ");
        message.append(getDisplaySubject()).append(" ").append(verb);
        for (Object part : messageParts) {
            message.append(" <").append(part).append(">");
        }
        failureStrategy.fail(message.toString());
    }

    /**
     * Assembles a failure message and passes it to the FailureStrategy
     *
     * @param verb the proposition being asserted
     * @param messageParts the expectations against which the subject is compared
     */
    protected void failWithBadResults(String verb, Object expected, String failVerb, Object actual) {
        String message = format("Not true that %s %s <%s>. It %s <%s>", getDisplaySubject(), verb, expected,
                failVerb, ((actual == null) ? "null reference" : actual));
        failureStrategy.fail(message);
    }

    /**
     * Assembles a failure message with an alternative representation of the wrapped subject
     * and passes it to the FailureStrategy
     *
     * @param verb the proposition being asserted
     * @param expected the expected value of the proposition
     * @param actual the custom representation of the subject to be reported in the failure.
     */
    protected void failWithCustomSubject(String verb, Object expected, Object actual) {
        String message = format("Not true that <%s> %s <%s>", ((actual == null) ? "null reference" : actual), verb,
                expected);
        failureStrategy.fail(message);
    }

    /**
     * Assembles a failure message without a given subject and passes it to the FailureStrategy
     *
     * @param verb the proposition being asserted
     */
    protected void failWithoutSubject(String verb) {
        String subject = this.customName == null ? "the subject" : "\"" + customName + "\"";
        failureStrategy.fail(format("Not true that %s %s", subject, verb));
    }

    /**
     * Passes through a failure message verbatim.  Used for {@link Subject} subclasses which
     * need to provide alternate language for more fit-to-purpose error messages.
     *
     *
     * @param message the message template to be passed to the failure.  Note, this method only
     *     guarantees to process {@code %s} tokens.  It is not guaranteed to be compatible
     *     with {@code String.format()}.  Any other formatting desired (such as floats or
     *     scientific notation) should be performed before the method call and the formatted
     *     value passed in as a string.
     * @param paramters the object parameters which will be applied to the message template.
     */
    protected void failWithRawMessage(String message, Object... parameters) {
        failureStrategy.fail(format(message, parameters));
    }

    @GwtIncompatible("java.lang.reflect.Field")
    public HasField hasField(final String fieldName) {
        final T subject = getSubject();
        if (subject == null) {
            failureStrategy.fail("Cannot determine a field name from a null object.");
            // Needed for Expect and other non-terminal failure strategies
            return new HasField() {
                @Override
                public void withValue(Object value) {
                    Subject.this.fail("Cannot test the presence of a value in a null object.");
                }
            };
        }
        final Class<?> subjectClass = subject.getClass();
        final Field field;
        try {
            field = ReflectionUtil.getField(subjectClass, fieldName);
            field.setAccessible(true);
        } catch (NoSuchFieldException e) {
            StringBuilder message = new StringBuilder("Not true that ");
            message.append("<").append(subjectClass.getSimpleName()).append(">");
            message.append(" has a field named <").append(fieldName).append(">");
            failureStrategy.fail(message.toString());

            // Needed for Expect and other non-terminal failure strategies
            return new HasField() {
                @Override
                public void withValue(Object value) {
                    Subject.this.fail("Cannot test the presence of a value in a non-present field.");
                }
            };
        }
        return new HasField() {
            @Override
            public void withValue(Object expected) {
                try {
                    Object actual = field.get(subject);
                    if (expected == actual || (expected != null && expected.equals(actual))) {
                        return;
                    } else {
                        StringBuilder message = new StringBuilder("Not true that ");
                        message.append("<").append(subjectClass.getSimpleName()).append(">'s");
                        message.append(" field <").append(fieldName).append(">");
                        message.append(" contains expected value <").append(expected).append(">.");
                        message.append(" It contains value <").append(actual).append(">");
                        failureStrategy.fail(message.toString());
                    }
                } catch (IllegalArgumentException e) {
                    throw new RuntimeException(
                            "Error checking field " + fieldName + " while testing for value " + expected);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(
                            "Cannot access field " + fieldName + " to test for value " + expected);
                }
            }
        };
    }

    @GwtIncompatible("java.lang.reflect.Field")
    public static interface HasField {
        /**
         * Supplementary assertion in which a present field can be tested
         * to determine if it contains a given value.
         */
        void withValue(Object value);
    }

    /**
     * @deprecated This method is not a proposition, but the default Object equality method.
     *     Testing code should use "is" or "isEqualTo" propositions for equality tests.
     */
    @Deprecated
    @Override
    public boolean equals(Object o) {
        isEqualTo(o);
        return false;
    }

    /**
     * @deprecated Equals/Hashcode is not supported on Subjects. Their only use is as a holder of
     *     propositions. Use of equals() is deprecated and forwards to isEqualTo() and
     *     hashCode() is disallowed.
     */
    @Deprecated
    @Override
    public int hashCode() {
        throw new UnsupportedOperationException(
                "" + "Equals/Hashcode is not supported on Subjects. Their only use is as a holder of "
                        + "propositions. Use of equals() is deprecated and forwards to isEqualTo() and "
                        + "hashCode() is disallowed.");
    }
}