001    /*
002     * Copyright 2002-2004 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.rules.constraint;
017    
018    import java.lang.reflect.Method;
019    
020    import org.springframework.rules.constraint.Constraint;
021    import org.springframework.rules.reporting.TypeResolvable;
022    import org.springframework.util.Assert;
023    
024    /**
025     * A adapter that can adapt a method on an object that accepts a single argument
026     * and returns a boolean result a UnaryPredicate. For example, a DAO might have
027     * the method <code>isUnique(String objectName)<code> that
028     * tests whether not a name parameter is unique.  To adapt that method as a
029     * UnaryPredicate, use this class.
030     *
031     * @author Keith Donald
032     */
033    public class MethodInvokingConstraint implements Constraint, TypeResolvable {
034    
035            private Object targetObject;
036    
037            private Method testMethod;
038    
039            private String type;
040    
041            /**
042             * Creates a MethodInvokingConstraint for the provided target object - the
043             * constraint logic is encapsulated within the specified method name.
044             *
045             * Note: this constructor will attempt to guess the parameter type for the
046             * method as it accept a single unary argument and return a boolean result.
047             *
048             * @param targetObject
049             *            The target object
050             * @param methodName
051             *            The method name
052             */
053            public MethodInvokingConstraint(Object targetObject, String methodName) {
054                    this(targetObject, methodName, null, null);
055            }
056    
057            public MethodInvokingConstraint(Object targetObject, String methodName,
058                            String constraintType) {
059                    this(targetObject, methodName, null, constraintType);
060            }
061    
062            private Class guessParameterType(Object object, String methodName) {
063                    Method[] methods = targetObject.getClass().getMethods();
064                    for (int i = 0; i < methods.length; i++) {
065                            Method m = methods[i];
066                            if (m.getName().equals(methodName)) {
067                                    Class[] types = m.getParameterTypes();
068                                    if (types.length == 1) {
069                                            return types[0];
070                                    }
071                            }
072                    }
073                    throw new IllegalArgumentException(
074                                    "No single argument, boolean method found with name '"
075                                    + methodName + "'");
076            }
077    
078            public MethodInvokingConstraint(Object targetObject, String methodName,
079                            Class parameterType) {
080                    this(targetObject, methodName, parameterType, null);
081            }
082    
083            public MethodInvokingConstraint(Object targetObject, String methodName,
084                            Class parameterType, String constraintType) {
085                    Assert.notNull(targetObject, "targetObject is required");
086                    this.targetObject = targetObject;
087                    if (parameterType == null) {
088                            parameterType = guessParameterType(targetObject, methodName);
089                    }
090                    setType(constraintType);
091                    try {
092                            this.testMethod = targetObject.getClass().getMethod(methodName,
093                                            new Class[]{parameterType});
094                    }
095                    catch (NoSuchMethodException e) {
096                            throw new RuntimeException(e);
097                    }
098                    Class returnType = testMethod.getReturnType();
099                    Assert.isTrue(returnType == Boolean.class
100                                    || returnType == boolean.class, "Return type must be a boolean type");
101            }
102    
103            public String getType() {
104                    return type;
105            }
106    
107            public void setType(String type) {
108                    this.type = type;
109            }
110    
111            public boolean test(Object argument) {
112                    try {
113                            return ((Boolean) testMethod.invoke(targetObject,
114                                            new Object[]{argument})).booleanValue();
115                    }
116                    catch (Exception e) {
117                            throw new RuntimeException(e);
118                    }
119            }
120    }