Skip to content

Instantly share code, notes, and snippets.

@zacscott
Last active December 18, 2015 14:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zacscott/5795983 to your computer and use it in GitHub Desktop.
Save zacscott/5795983 to your computer and use it in GitHub Desktop.
A simple facility to support design-by-contract principles (in Java).
package net.zeddev.util;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* A simple facility to support design-by-contract principles.
* Significantly smaller and simpler than other more heavy-weight frameworks
* (such as contract2j, OVal, etc) but provides better functionality over the
* intrinsic Java {@code assert}.
* </p>
*
* <p>
* The methods work with with the existing Java assertion facility, making it
* simple and portable to use (for instance with IDE's & build automation
* tools). No special handling to enable/disable (just enable assertions in the
* VM, using the {@code -ea} switch).
* </p>
*
* <p>
* All invariant assertions provided as static methods in the {@code Assertions}
* class. There are three types/categories of assertion which are;
* <ul>
* <li>Preconditions - invariant condition before executing a method
* ({@code require} prefix).</li>
* <li>General - a general invariant condition in main body of a method
* ({@code check} prefix).</li>
* <li>Postconditions - invariant condition after executing body of a method
* ({@code ensure} prefix).</li>
* </ul>
* </p>
*
* <p>
* There are also three types of condition;
* <ul>
* <li>general/predicate (no suffix)</li>
* <li>null check ({@code NotNull} suffix)</li>
* <li>equality ({@code Equals} suffix)</li>
* </ul>
* </p>
*
* <p>
* There are also three types of condition;
* <ul>
* <li>general/predicate (no suffix)</li>
* <li>null check ({@code NotNull} suffix)</li>
* <li>equality ({@code Equals } or {@code NotEquals} suffix)</li>
* </ul>
* </p>
*
* <p>
* The assertion methods are named using the following format;
* {@code type condition-type} using camel case. So for instance;
* <br />
* <pre>
* requireNotNull(...); // precondition null check
* checkNotNull(...); // general null check
* ensureEquals(...); // postcondition equality check
* // etc...
* </pre>
* </p>
*
* <p>
* The builtin {@code assert} can be used in conjunction with the
* {@code Assertions} methods to reproduce traditional assertion behavior,
* where the statement is not executed when assertions are not enabled.
* This is done like so;
* </p>
*
* <p>
* <pre>
* assert require(...);
* assert checkNotNull(...);
* etc.
* </pre>
* </p>
*
* <p>
* <b>NOTE:</b> This file is released as a stand-alone utility. The original
* file and the associated unit test can be found on GitHub Gist -
* <a href="https://gist.github.com/zscott92/5795983">here</a>.
* </p>
*
* @author Zachary Scott <zscott.dev@gmail.com>
*/
public final class Assertions {
/**
* Checks a precondition.
*
* @param cond The condition that should be met.
* @param desc A description of the error, when {@code cond}
* is {@code false} (using the {@code String.format()} format).
* Cannot be {@code null}.
* @param args The arguments to go with the formatted description.
*/
public static boolean require(boolean cond, String desc, Object... args) {
assert requireNotNull(desc);
if (assertionsEnabled() && !cond) {
throw fromCaller(new PreconditionException(
String.format(desc, args)
));
}
return true;
}
/**
* Checks a precondition.
*
* @param cond The condition that should be met.
*/
public static boolean require(boolean cond) {
return require(cond, "");
}
/**
* Checks that an object reference is not {@code null} (as a precondition).
*
* @param object The object reference to be checked.
* @param name The name of the object.
*/
public static boolean requireNotNull(Object object, String name) {
if (assertionsEnabled() && object == null) {
throw fromCaller(new PreconditionException(
nullExceptionMessage(name)
));
}
return true;
}
/**
* Checks that an object reference is not {@code null} (as a precondition).
*
* @param object The object reference to be checked.
*/
public static boolean requireNotNull(Object object) {
return requireNotNull(object, "");
}
/**
* Checks that an object has an expected value (as a precondition).
*
* @param object The object to be checked (must not be {@code null}).
* @param expected The expected value of the {@code object} (must not be
* {@code null}).
* @param desc A description of the error when the object does not have the
* expected value (using the {@code String.format()} format).
* Cannot be {@code null}.
* @param args The arguments to go with the formatted description.
*/
public static boolean requireEquals(
Object object,
Object expected,
String desc,
Object... args) {
assert requireNotNull(object);
assert requireNotNull(expected);
assert requireNotNull(desc);
return require(object.equals(expected), desc, args);
}
/**
* Checks that an object has an expected value (as a precondition).
*
* @param object The object to be checked (must not be {@code null}).
* @param expected The expected value of the {@code object} (must not be
* {@code null}).
*/
public static boolean requireEquals(Object object, Object expected) {
assert requireNotNull(object);
assert requireNotNull(expected);
if (assertionsEnabled() && !object.equals(expected)) {
throw fromCaller(new PreconditionException(
equalityExceptionMessage(object, expected)
));
}
return true;
}
/**
* Checks that an object does not have the given value (as a precondition).
*
* @param object The object to be checked (must not be {@code null}).
* @param value The value to compare with {@code object} (must not be
* {@code null}).
* @param desc A description of the error when the object does not have the
* expected value (using the {@code String.format()} format).
* Cannot be {@code null}.
* @param args The arguments to go with the formatted description.
*/
public static boolean requireNotEquals(
Object object,
Object value,
String desc,
Object... args) {
assert requireNotNull(object);
assert requireNotNull(value);
assert requireNotNull(desc);
return require(!object.equals(value), desc, args);
}
/**
* Checks that an object does not have the given value (as a precondition).
*
* @param object The object to be checked (must not be {@code null}).
* @param value The value to compare with {@code object} (must not be
* {@code null}).
*/
public static boolean requireNotEquals(Object object, Object value) {
assert requireNotNull(object);
assert requireNotNull(value);
if (assertionsEnabled() && object.equals(value)) {
throw fromCaller(new PreconditionException(
equalityExceptionMessage(object, value)
));
}
return true;
}
/**
* Checks a general invariant.
*
* @param cond The condition that should be met.
* @param desc A description of the error, when {@code cond}
* is {@code false} (using the {@code String.format()} format).
* Cannot be {@code null}.
* @param args The arguments to go with the formatted description.
*/
public static boolean check(boolean cond, String desc, Object... args) {
assert requireNotNull(desc);
if (assertionsEnabled() && !cond) {
throw fromCaller(new InvariantException(
String.format(desc, args)
));
}
return true;
}
/**
* Checks a general invariant.
*
* @param cond The condition that should be met.
*/
public static boolean check(boolean cond) {
return check(cond, "");
}
/**
* Checks that an object reference is not {@code null}.
*
* @param object The object reference to be checked.
* @param name The name of the object.
*/
public static boolean checkNotNull(Object object, String name) {
if (assertionsEnabled() && object == null) {
throw fromCaller(new InvariantException(
nullExceptionMessage(name)
));
}
return true;
}
/**
* Checks that an object reference is not {@code null}.
*
* @param object The object reference to be checked.
*/
public static boolean checkNotNull(Object object) {
return checkNotNull(object, "");
}
/**
* Checks that an object has an expected value.
*
* @param object The object to be checked (must not be {@code null}).
* @param expected The expected value of the {@code object} (must not be
* {@code null}).
* @param desc A description of the error when the object does not have the
* expected value (using the {@code String.format()} format).
* Cannot be {@code null}.
* @param args The arguments to go with the formatted description.
*/
public static boolean checkEquals(
Object object,
Object expected,
String desc,
Object... args) {
assert requireNotNull(object);
assert requireNotNull(expected);
assert requireNotNull(desc);
return check(object.equals(expected), desc, args);
}
/**
* Checks that an object has an expected value.
*
* @param object The object to be checked (must not be {@code null}).
* @param expected The expected value of the {@code object} (must not be
* {@code null}).
*/
public static boolean checkEquals(Object object, Object expected) {
assert requireNotNull(object);
assert requireNotNull(expected);
if (assertionsEnabled() && !object.equals(expected)) {
throw fromCaller(new InvariantException(
equalityExceptionMessage(object, expected)
));
}
return true;
}
/**
* Checks that an object does not have the given value.
*
* @param object The object to be checked (must not be {@code null}).
* @param desc A description of the error when the object does not have the
* expected value (using the {@code String.format()} format).
* Cannot be {@code null}.
* @param args The arguments to go with the formatted description.
*/
public static boolean checkNotEquals(
Object object,
Object value,
String desc,
Object... args) {
assert requireNotNull(object);
assert requireNotNull(value);
assert requireNotNull(desc);
return check(!object.equals(value), desc, args);
}
/**
* Checks that an object does not have the given value.
*
* @param object The object to be checked (must not be {@code null}).
* @param value The value to compare with {@code object} (must not be
* {@code null}).
*/
public static boolean checkNotEquals(Object object, Object value) {
assert requireNotNull(object);
assert requireNotNull(value);
if (assertionsEnabled() && object.equals(value)) {
throw fromCaller(new InvariantException(
equalityExceptionMessage(object, value)
));
}
return true;
}
/**
* Checks a postcondition.
*
* @param cond The condition that should be met.
* @param desc A description of the error, when {@code cond}
* is {@code false} (using the {@code String.format()} format).
* Cannot be {@code null}.
* @param args The arguments to go with the formatted description.
*/
public static boolean ensure(boolean cond, String desc, Object... args) {
assert requireNotNull(desc);
if (assertionsEnabled() && !cond) {
throw fromCaller(new PostconditionException(
String.format(desc, args)
));
}
return true;
}
/**
* Checks a postcondition.
*
* @param cond The condition that should be met.
*/
public static boolean ensure(boolean cond) {
return ensure(cond, "");
}
/**
* Checks that an object reference is not {@code null} (as a postcondition).
*
* @param object The object reference to be checked.
* @param name The name of the object.
*/
public static boolean ensureNotNull(Object object, String name) {
if (assertionsEnabled() && object == null) {
throw fromCaller(new PostconditionException(
nullExceptionMessage(name)
));
}
return true;
}
/**
* Checks that an object reference is not {@code null} (as a postcondition).
*
* @param object The object reference to be checked.
*/
public static boolean ensureNotNull(Object object) {
return ensureNotNull(object, "");
}
/**
* Checks that an object has an expected value (as a postcondition).
*
* @param object The object to be checked (must not be {@code null}).
* @param expected The expected value of the {@code object} (must not be
* {@code null}).
* @param desc A description of the error when the object does not have the
* expected value (using the {@code String.format()} format).
* Cannot be {@code null}.
* @param args The arguments to go with the formatted description.
*/
public static boolean ensureEquals(
Object object,
Object expected,
String desc,
Object... args) {
assert requireNotNull(object);
assert requireNotNull(expected);
assert requireNotNull(desc);
return ensure(object.equals(expected), desc, args);
}
/**
* Checks that an object has an expected value (as a postcondition).
*
* @param object The object to be checked (must not be {@code null}).
* @param expected The expected value of the {@code object} (must not be
* {@code null}).
*/
public static boolean ensureEquals(Object object, Object expected) {
assert requireNotNull(object);
assert requireNotNull(expected);
if (assertionsEnabled() && !object.equals(expected)) {
throw fromCaller(new PostconditionException(
equalityExceptionMessage(object, expected)
));
}
return true;
}
/**
* Checks that an object does not have the given value (as a precondition).
*
* @param object The object to be checked (must not be {@code null}).
* @param value The value to compare with {@code object} (must not be
* {@code null}).
* @param desc A description of the error when the object does not have the
* expected value (using the {@code String.format()} format).
* Cannot be {@code null}.
* @param args The arguments to go with the formatted description.
*/
public static boolean ensureNotEquals(
Object object,
Object value,
String desc,
Object... args) {
assert requireNotNull(object);
assert requireNotNull(value);
assert requireNotNull(desc);
return ensure(!object.equals(value), desc, args);
}
/**
* Checks that an object does not have the given value (as a precondition).
*
* @param object The object to be checked (must not be {@code null}).
* @param value The value to compare with {@code object} (must not be
* {@code null}).
*/
public static boolean ensureNotEquals(Object object, Object value) {
assert requireNotNull(object);
assert requireNotNull(value);
if (assertionsEnabled() && object.equals(value)) {
throw fromCaller(new PostconditionException(
equalityExceptionMessage(object, value)
));
}
return true;
}
// builds a string describing a failed null pointer condition
private static String nullExceptionMessage(String name) {
if (name == null) {
return "Null reference";
} else {
return String.format("%s must not be null", name);
}
}
// builds a string describing an equality exception
private static String equalityExceptionMessage(
Object object,
Object expected) {
StringBuilder msg = new StringBuilder();
msg.append("Expected \"");
msg.append(expected.toString());
msg.append("\" but was \"");
msg.append(object.toString());
msg.append("\"");
return msg.toString();
}
// checks whether assertions are enabled
private static boolean assertionsEnabled() {
boolean enabled = false;
assert(enabled = true);
return enabled;
}
// sets the exception stack trace from the callers scope
// (i.e removing internal calls from within this class)
private static <E extends Throwable> E fromCaller(E ex) {
List<StackTraceElement> stackTrace = new ArrayList<StackTraceElement>();
// add all calls, not from this class
for (StackTraceElement element : ex.getStackTrace()) {
if (!element.getClassName().equals(Assertions.class.getName()))
stackTrace.add(element);
}
// change to modified stack trace
ex.setStackTrace(
stackTrace.toArray(
new StackTraceElement[stackTrace.size()]
)
);
return ex;
}
/** Thrown when a precondition is not met (asserted with a call
* to a {@code require*} method). */
public static class PreconditionException extends AssertionError {
/** Creates a new {@code PreconditionException}, with description. */
public PreconditionException(String msg) {
super(String.format(
"Precondition not met; %s", msg
));
}
}
/** Thrown when a invariant condition is not met (asserted with a call
* to a {@code check*} method). */
public static class InvariantException extends AssertionError {
/** Creates a new {@code InvariantException}, with description. */
public InvariantException(String msg) {
super(msg);
}
}
/** Thrown when a postcondition is not met (asserted with a call
* to an {@code ensure*} method). */
public static class PostconditionException extends AssertionError {
/** Creates a new {@code PostconditionException}, with description. */
public PostconditionException(String msg) {
super(String.format(
"Postcondition not met; %s", msg
));
}
}
}
/* Copyright (C) 2013 Zachary Scott <zscott.dev@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
package net.zeddev.util.test;
import static org.junit.Assert.*;
import static net.zeddev.util.Assertions.*;
import org.junit.Test;
/**
* Unit test for {@code net.zeddev.util.Assertions}.
*
* <p>
* <b>NOTE:</b> The {@code Assertions} class is released as a stand-alone utility and
* the original file can be found on GitHub Gist -
* <a href="https://gist.github.com/zscott92/5795983">here</a>.
* </p>
*
* @author Zachary Scott <zscott.dev@gmail.com>
*/
public class AssertionsTest {
/** Test passing conditions on assertion methods. */
@Test
public void testAssertTrue() throws Throwable {
// NOTE all of the following assertions should pass
Object testObject = "Test object";
// precondition assertions
require(true, "desc");
require(true);
requireNotNull(testObject, "name");
requireNotNull(testObject);
requireEquals(testObject, testObject, "desc");
requireEquals(testObject, testObject);
requireNotEquals(testObject, "not equals");
// general invariant assertions
check(true, "desc");
check(true);
checkNotNull(testObject, "name");
checkNotNull(testObject);
checkEquals(testObject, testObject, "desc");
checkEquals(testObject, testObject);
checkNotEquals(testObject, "not equals");
// postcondition assertions
ensure(true, "desc");
ensure(true);
ensureNotNull(testObject, "name");
ensureNotNull(testObject);
ensureEquals(testObject, testObject, "desc");
ensureEquals(testObject, testObject);
ensureNotEquals(testObject, "not equals");
}
/** Test fail conditions on assertion methods. */
@Test
public void testAssertFail() throws Throwable {
// precondition assertions
try {
require(false);
fail("Assertions.require() fail condition not caught!");
} catch (PreconditionException ex) { }
try {
requireNotNull(null);
fail("Assertions.requireNotNull() fail condition not caught!");
} catch (PreconditionException ex) { }
try {
requireEquals("test1", "test2");
fail("Assertions.requireEquals() fail condition not caught!");
} catch (PreconditionException ex) { }
try {
requireEquals("test1", "test2", "desc");
fail("Assertions.requireEquals() fail condition not caught!");
} catch (PreconditionException ex) { }
try {
requireNotEquals("test1", "test1", "desc");
fail("Assertions.requireNotEquals() fail condition not caught!");
} catch (PreconditionException ex) { }
// general assertions
try {
check(false);
fail("Assertions.check() fail condition not caught!");
} catch (InvariantException ex) { }
try {
checkNotNull(null);
fail("Assertions.checkNotNull() fail condition not caught!");
} catch (InvariantException ex) { }
try {
checkEquals("test1", "test2");
fail("Assertions.checkEquals() fail condition not caught!");
} catch (InvariantException ex) { }
try {
checkEquals("test1", "test2", "desc");
fail("Assertions.checkEquals() fail condition not caught!");
} catch (InvariantException ex) { }
try {
checkNotEquals("test1", "test1", "desc");
fail("Assertions.checkNotEquals() fail condition not caught!");
} catch (InvariantException ex) { }
// precondition assertions
try {
ensure(false);
fail("Assertions.ensure() fail condition not caught!");
} catch (PostconditionException ex) { }
try {
ensureNotNull(null);
fail("Assertions.ensureNotNull() fail condition not caught!");
} catch (PostconditionException ex) { }
try {
ensureEquals("test1", "test2");
fail("Assertions.ensureEquals() fail condition not caught!");
} catch (PostconditionException ex) { }
try {
ensureEquals("test1", "test2", "desc");
fail("Assertions.ensureEquals() fail condition not caught!");
} catch (PostconditionException ex) { }
try {
ensureNotEquals("test1", "test1", "desc");
fail("Assertions.ensureNotEquals() fail condition not caught!");
} catch (PostconditionException ex) { }
}
/** Test calling with assert builtin. */
@Test
public void testCallWithAssert() throws Throwable {
// NOTE all of the following assertions should pass
Object testObject = "Test object";
// precondition assertions
assert require(true, "desc");
assert require(true);
assert requireNotNull(testObject, "name");
assert requireNotNull(testObject);
assert requireEquals(testObject, testObject, "desc");
assert requireEquals(testObject, testObject);
assert requireNotEquals(testObject, "not equals");
// general invariant assertions
assert check(true, "desc");
assert check(true);
assert checkNotNull(testObject, "name");
assert checkNotNull(testObject);
assert checkEquals(testObject, testObject, "desc");
assert checkEquals(testObject, testObject);
assert checkNotEquals(testObject, "not equals");
// postcondition assertions
assert ensure(true, "desc");
assert ensure(true);
assert ensureNotNull(testObject, "name");
assert ensureNotNull(testObject);
assert ensureEquals(testObject, testObject, "desc");
assert ensureEquals(testObject, testObject);
assert ensureNotEquals(testObject, "not equals");
}
/** Test whether the internal {@code fromCaller} correctly alters exception stack trace. */
@Test
public void testFromCaller() throws Throwable {
try {
check(false);
// NOTE should throw exception here
} catch (AssertionError e) {
StackTraceElement[] stackTrace = e.getStackTrace();
StackTraceElement top = stackTrace[0]; // top element
// check that the element on top of the stack is this method
assertEquals(top.getClassName(), getClass().getName());
assertEquals(top.getMethodName(), "testFromCaller");
}
}
}
/* Copyright (C) 2013 Zachary Scott <zscott.dev@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment