/*
* Copyright 2015 S. Webber
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.oakgp.node;
import org.oakgp.Arguments;
import org.oakgp.Assignments;
import org.oakgp.Type;
import org.oakgp.function.Function;
/** Contains a function (operator) and the arguments (operands) to apply to it. */
public final class FunctionNode implements Node {
/**
* A sequence of prime numbers.
* <p>
* Used to generate the {@code hashCode} value. This is used - rather than calling {@code java.util.Arrays.hashCode(Object a[])} with the node's arguments -
* so that, for example, the two expressions {@code (- (- (* -1 v3) 0) (- 13 v1))} and {@code (- (- (* -1 v3) 13) (- 0 v1))} have different hash code values.
* See {@code org.oakgp.node.FunctionNodeTest.testHashCode()} for more details.
*/
private static final int[] PRIMES = { 2, 3, 5, 7, 11, 13, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 };
private final Function function;
private final Arguments arguments;
private final int nodeCount;
private final int hashCode;
/**
* Constructs a new {@code FunctionNode} with the specified function function and arguments.
*
* @param function
* the function to associate with this {@code FunctionNode}
* @param arguments
* the arguments (i.e. operands) to apply to {@code function} when evaluating this {@code FunctionNode}
*/
public FunctionNode(Function function, Node... arguments) {
this(function, Arguments.createArguments(arguments));
}
/**
* Constructs a new {@code FunctionNode} with the specified function function and arguments.
*
* @param function
* the function to associate with this {@code FunctionNode}
* @param arguments
* the arguments (i.e. operands) to apply to {@code function} when evaluating this {@code FunctionNode}
*/
public FunctionNode(Function function, Arguments arguments) {
this.function = function;
this.arguments = arguments;
this.nodeCount = calculateNodeCount(arguments);
this.hashCode = (function.getClass().getName().hashCode() * 31) * createHashCode(arguments, nodeCount);
}
private static int calculateNodeCount(Arguments arguments) {
int total = 1;
for (int i = 0; i < arguments.getArgCount(); i++) {
total += arguments.getArg(i).getNodeCount();
}
return total;
}
private static int createHashCode(Arguments arguments, int nodeCount) {
int hashCode = 0;
int primesIdx = 0;
for (int i = 0; i < arguments.getArgCount(); i++) {
hashCode += arguments.getArg(i).hashCode() * (PRIMES[primesIdx] + nodeCount);
if (++primesIdx == PRIMES.length) {
primesIdx = 0;
}
}
return hashCode;
}
public Function getFunction() {
return function;
}
public Arguments getArguments() {
return arguments;
}
@SuppressWarnings("unchecked")
@Override
public Object evaluate(Assignments assignments) {
return function.evaluate(arguments, assignments);
}
@Override
public int getNodeCount() {
return nodeCount;
}
@Override
public int getHeight() {
// TODO it may be beneficial to cache this result on its first call
int height = 0;
for (int i = 0; i < arguments.getArgCount(); i++) {
height = Math.max(height, arguments.getArg(i).getHeight());
}
return height + 1;
}
@Override
public Type getType() {
return function.getSignature().getReturnType();
}
@Override
public NodeType getNodeType() {
return NodeType.FUNCTION;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o == null || this.hashCode != o.hashCode()) {
return false;
} else if (o instanceof FunctionNode) {
FunctionNode fn = (FunctionNode) o;
// NOTE if we often return false here then that indicates hashCode() could be improved
return this.function == fn.function && this.arguments.equals(fn.arguments);
} else {
return false;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('(').append(function.getDisplayName());
for (int i = 0; i < arguments.getArgCount(); i++) {
sb.append(' ').append(arguments.getArg(i));
}
return sb.append(')').toString();
}
}