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