001 package org.nakedobjects.applib.spec;
002
003 import java.lang.reflect.Method;
004
005
006 /**
007 * Adapter to make it easy to write {@link Specification}s.
008 *
009 * <p>
010 * Provides two main features:
011 * <ul>
012 * <li> first, is type-safe (with invalid type being either ignored or constituting a failure), and
013 * <li> second, checks for nulls (with a null either being ignore or again constituting a failure)
014 * </ul>
015 *
016 * <p>
017 * Implementation note: inspired by (borrowed code from) Hamcrest's <tt>TypeSafeMatcher</tt>.
018 */
019 public abstract class AbstractSpecification<T> implements Specification {
020
021 public enum TypeChecking {
022 ENSURE_CORRECT_TYPE,
023 IGNORE_INCORRECT_TYPE,
024 }
025
026 public enum Nullability {
027 ENSURE_NOT_NULL,
028 IGNORE_IF_NULL
029 }
030
031 private static Class<?> findExpectedType(Class<?> fromClass) {
032 for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) {
033 for (Method method : c.getDeclaredMethods()) {
034 if (isSatisfiesSafelyMethod(method)) {
035 return method.getParameterTypes()[0];
036 }
037 }
038 }
039
040 throw new Error("Cannot determine correct type for satisfiesSafely() method.");
041 }
042
043 private static boolean isSatisfiesSafelyMethod(Method method) {
044 return method.getName().equals("satisfiesSafely")
045 && method.getParameterTypes().length == 1
046 && !method.isSynthetic();
047 }
048
049
050 private final Class<?> expectedType;
051 private final Nullability nullability;
052 private final TypeChecking typeChecking;
053
054 protected AbstractSpecification() {
055 this(Nullability.IGNORE_IF_NULL, TypeChecking.IGNORE_INCORRECT_TYPE);
056 }
057
058 protected AbstractSpecification(final Nullability nullability, final TypeChecking typeChecking) {
059 this.expectedType = findExpectedType(getClass());
060 this.nullability = nullability;
061 this.typeChecking = typeChecking;
062 }
063
064
065 /**
066 * Checks not null and is correct type, and delegates to {@link #satisfiesSafely(Object)}.
067 */
068 @SuppressWarnings({"unchecked"})
069 public final String satisfies(final Object obj) {
070 if (obj == null) {
071 return nullability == Nullability.IGNORE_IF_NULL? null: "Cannot be null";
072 }
073 if (!expectedType.isInstance(obj)) {
074 return typeChecking == TypeChecking.IGNORE_INCORRECT_TYPE? null: "Incorrect type";
075 }
076 T objAsT = (T) obj;
077 return satisfiesSafely(objAsT);
078 }
079
080 /**
081 * If <tt>null</tt> then satisfied, otherwise is reason why the specification is
082 * not satisfied.
083 *
084 * <p>
085 * Subclasses should implement this. The item will already have been checked for
086 * the specific type and will never be null.
087 */
088 public abstract String satisfiesSafely(T obj);
089
090
091 }