001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */ 
017    package org.apache.commons.betwixt.expression;
018    
019    import java.lang.reflect.Method;
020    
021    /** <p><code>MethodExpression</code> evaluates a method on the current bean context.</p>
022      *
023      * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
024      * @version $Revision: 471234 $
025      */
026    public class MethodExpression implements Expression {
027    
028        /** null arguments */
029        protected static Object[] NULL_ARGUMENTS;
030        /** null classes */
031        protected static Class[] NULL_CLASSES;
032        
033        /** The method to call on the bean */
034        private Method method;
035        
036        /** Base constructor */
037        public MethodExpression() {
038        }
039        
040        /**  
041         * Convenience constructor sets method property 
042         * @param method the Method whose return value when invoked on the bean 
043         * will the value of this expression
044         */
045        public MethodExpression(Method method) {
046            this.method = method;
047        }
048    
049        /** 
050         * Evaluate by calling the read method on the current bean 
051         *
052         * @param context the context against which this expression will be evaluated
053         * @return the value returned by the method when it's invoked on the context's bean,
054         * so long as the method can be invoked.
055         * Otherwise, null.
056         */
057        public Object evaluate(Context context) {
058            Object bean = context.getBean();
059            if ( bean != null ) {
060                Object[] arguments = getArguments();
061                try {
062                    return method.invoke( bean, arguments );
063                    
064                } catch (IllegalAccessException e) {
065                    // lets try use another method with the same name
066                    Method alternate = null;
067                    try {
068                        Class type = bean.getClass();
069                        alternate = findAlternateMethod( type, method );
070                        if ( alternate != null ) {
071                            try
072                            {
073                                return alternate.invoke( bean, arguments );
074                            } catch (IllegalAccessException ex) {
075                                alternate.setAccessible(true);
076                                return alternate.invoke( bean, arguments );
077                            }
078                        }
079                        else
080                        {
081                            method.setAccessible(true);
082                            return method.invoke( bean, arguments );
083                        }
084                    } catch (Exception e2) {
085                        handleException(context, e2, alternate);
086                    }
087                } catch (Exception e) {
088                    handleException(context, e, method);
089                }
090            }
091            return null;
092        }
093    
094        /** 
095         * Do nothing.
096         * @see org.apache.commons.betwixt.expression.Expression
097         */
098        public void update(Context context, String newValue) {
099            // do nothing
100        }
101    
102        /** 
103         * Gets the method used to evaluate this expression.
104         * @return the method whose value (when invoked against the context's bean) will be used 
105         * to evaluate this expression.
106         */
107        public Method getMethod() {
108            return method;
109        }
110        
111        /** 
112         * Sets the method used to evaluate this expression 
113         * @param method method whose value (when invoked against the context's bean) will be used 
114         * to evaluate this expression 
115         */
116        public void setMethod(Method method) {
117            this.method = method;
118        }
119        
120        // Implementation methods
121        //-------------------------------------------------------------------------    
122        
123        /** 
124         * Allows derived objects to create arguments for the method call 
125         * @return {@link #NULL_ARGUMENTS}
126         */
127        protected Object[] getArguments() {
128            return NULL_ARGUMENTS;
129        }
130        
131        /** Tries to find an alternate method for the given type using interfaces
132          * which gets around the problem of inner classes, 
133          * such as on Map.Entry implementations.
134          *
135          * @param type the Class whose methods are to be searched
136          * @param method the Method for which an alternative is to be search for
137          * @return the alternative Method, if one can be found. Otherwise null.
138          */
139        protected Method findAlternateMethod( 
140                                                Class type, 
141                                                Method method ) {
142            // XXX
143            // Would it be better to use the standard reflection code in eg. lang
144            // since this code contains workarounds for common JVM bugs?
145            //
146            Class[] interfaces = type.getInterfaces();
147            if ( interfaces != null ) {
148                String name = method.getName();
149                for ( int i = 0, size = interfaces.length; i < size; i++ ) {
150                    Class otherType = interfaces[i];
151                    //
152                    // catch NoSuchMethodException so that all interfaces will be tried
153                    try {
154                        Method alternate = otherType.getMethod( name, NULL_CLASSES );
155                        if ( alternate != null && alternate != method ) {
156                            return alternate;
157                        }
158                    } catch (NoSuchMethodException e) {
159                        // swallow
160                    }
161                }
162            }
163            return null;
164        }
165        
166        /** 
167          * <p>Log error to context's logger.</p> 
168          *
169          * <p>Allows derived objects to handle exceptions differently.</p>
170          *
171          * @param context the Context being evaluated when the exception occured
172          * @param e the exception to handle
173          * @since 0.8
174          */
175        protected void handleException(Context context, Exception e, Method m) {
176            // use the context's logger to log the problem
177            context.getLog().error("[MethodExpression] Cannot evaluate method " + m, e);
178        }
179        
180        /** 
181         * <p>Log error to context's logger.</p> 
182         *
183         * <p>Allows derived objects to handle exceptions differently.</p>
184         *
185         * @param context the Context being evaluated when the exception occured
186         * @param e the exception to handle
187         */
188       protected void handleException(Context context, Exception e) {
189           // use the context's logger to log the problem
190           context.getLog().error("[MethodExpression] Cannot evaluate method ", e);
191       }
192        
193        /** 
194         * Returns something useful for logging.
195         * @return something useful for logging
196         */
197        public String toString() {
198            return "MethodExpression [method=" + method + "]";
199        }
200    }