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    }