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.functor.core.algorithm;
018    
019    import java.io.Serializable;
020    
021    import org.apache.commons.functor.Function;
022    
023    /**
024     * Tail recursion for {@link Function functions}. If the {@link Function}
025     * returns another function of type <code>functionType</code>, that function
026     * is executed. Functions are executed until a non function value or a
027     * function of a type other than that expected is returned.
028     */
029    public class RecursiveEvaluation implements Function<Object>, Serializable {
030        /**
031         * serialVersionUID declaration.
032         */
033        private static final long serialVersionUID = -7992078213921938619L;
034        private final Class<?> functionType;
035        private Function<?> function;
036    
037        /**
038         * Create a new RecursiveEvaluation. Recursion will continue while the
039         * returned value is of the same runtime class as <code>function</code>.
040         * @param function initial, potentially recursive Function
041         */
042        public RecursiveEvaluation(Function<?> function) {
043            this(function, getClass(function));
044        }
045    
046        /**
047         * Create a new RecursiveEvaluation.
048         * @param function initial, potentially recursive Function
049         * @param functionType as long as result is an instance, keep processing.
050         */
051        public RecursiveEvaluation(Function<?> function, Class<?> functionType) {
052            if (function == null) {
053                throw new IllegalArgumentException("Function argument was null");
054            }
055            if (functionType == null || !Function.class.isAssignableFrom(functionType)) {
056                throw new IllegalArgumentException(Function.class + " is not assignable from " + functionType);
057            }
058            this.function = function;
059            this.functionType = functionType;
060        }
061    
062        /**
063         * {@inheritDoc}
064         */
065        public final Object evaluate() {
066            Object result = null;
067            // if the function returns another function, execute it. stop executing
068            // when the result is not of the expected type.
069            while (true) {
070                result = function.evaluate();
071                if (functionType.isInstance(result)) {
072                    function = (Function<?>) result;
073                    continue;
074                } else {
075                    break;
076                }
077            }
078            return result;
079        }
080    
081        /**
082         * {@inheritDoc}
083         */
084        public final boolean equals(Object obj) {
085            if (obj == this) {
086                return true;
087            }
088            if (!(obj instanceof RecursiveEvaluation)) {
089                return false;
090            }
091            return ((RecursiveEvaluation) obj).function.equals(function);
092        }
093    
094        /**
095         * {@inheritDoc}
096         */
097        public int hashCode() {
098            return "RecursiveEvaluation".hashCode() << 2 ^ function.hashCode();
099        }
100    
101        /**
102         * Get the class of the specified object, or <code>null</code> if <code>o</code> is <code>null</code>.
103         * @param o Object to check
104         * @return Class found
105         */
106        private static <T> Class<?> getClass(Function<?> f) {
107            return f == null ? null : f.getClass();
108        }
109    }