final RuleInfo ruleInfo = getRuleInfo(output);
final List<MapEntry> trace = ruleInfo.getRuleExecutionTrace();
final Map<String, Object> validCoord = validateCoordinate(coordinate);
Map<String, Object> input = new CaseInsensitiveMap<>(validCoord);
boolean run = true;
trace.add(new MapEntry("begin: " + getName(), coordinate));
long numRulesExec = 0;
T lastExecutedCellValue = null;
while (run)
{
run = false;
final Map<String, List<Column>> boundCoordinates = bindCoordinateToAxes(input);
final String[] axisNames = getAxisNames(boundCoordinates);
final Map<String, Integer> counters = getCountersPerAxis(boundCoordinates);
final Set<Column> idCoord = new HashSet<>();
final Map<Long, Object[]> cachedConditionValues = new HashMap<>();
final Map<String, Integer> conditionsFiredCountPerAxis = new HashMap<>();
boolean done = false;
try
{
while (!done)
{
idCoord.clear();
Map<String, Object> ruleIds = new LinkedHashMap<>();
for (final String axisName : axisNames)
{
final List<Column> cols = boundCoordinates.get(axisName);
final Column boundColumn = cols.get(counters.get(axisName) - 1);
final Axis axis = axisList.get(axisName);
if (axis.getType() == AxisType.RULE)
{
Object conditionValue;
// Use Object[] to hold cached condition value to distinguish from a condition
// that returned null as it's value.
Object[] cachedConditionValue = cachedConditionValues.get(boundColumn.id);
if (cachedConditionValue == null)
{ // Has the condition on the Rule axis been run this execution? If not, run it and cache it.
CommandCell cmd = (CommandCell) boundColumn.getValue();
Map<String, Object> ctx = prepareExecutionContext(input, output);
// If the cmd == null, then we are looking at a default column on a rule axis.
// the conditionValue becomes 'true' for Default column when ruleAxisBindCount = 0
conditionValue = cmd == null ? isZero(conditionsFiredCountPerAxis.get(axisName)) : cmd.execute(ctx);
cachedConditionValues.put(boundColumn.id, new Object[]{conditionValue});
if (isTrue(conditionValue))
{ // Rule fired
Integer count = conditionsFiredCountPerAxis.get(axisName);
conditionsFiredCountPerAxis.put(axisName, count == null ? 1 : count + 1);
}
}
else
{ // re-use condition on this rule axis (happens when more than one rule axis on an n-cube)
conditionValue = cachedConditionValue[0];
}
// A rule column on a given axis can be accessed more than once (example: A, B, C on
// one rule axis, X, Y, Z on another). This generates coordinate combinations
// (AX, AY, AZ, BX, BY, BZ, CX, CY, CZ). The condition columns must be run only once, on
// subsequent access, the cached result of the condition is used.
if (isTrue(conditionValue))
{
bindColumn(idCoord, ruleIds, axis, boundColumn);
}
}
else
{
bindColumn(idCoord, ruleIds, axis, boundColumn);
}
}
// Step #2 Execute cell and store return value, associating it to the Axes and Columns it bound to
if (idCoord.size() == axisNames.length)
{ // Conditions on rule axes that do not evaluate to true, do not generate complete coordinates (intentionally skipped)
numRulesExec++;
MapEntry entry = new MapEntry(ruleIds, null);
try
{
lastExecutedCellValue = getCellById(idCoord, input, output);
entry.setValue(lastExecutedCellValue);
trace.add(entry);
}
catch (RuleStop e)
{ // Statement threw at RuleStop
entry.setValue("[RuleStop]");
trace.add(entry);
throw e;
}
catch(RuleJump e)
{ // Statement threw at RuleJump
entry.setValue("[RuleJump]");
trace.add(entry);
throw e;
}
catch (Exception e)
{
String msg = e.getMessage();
if (StringUtilities.isEmpty(msg))
{
msg = e.getClass().getName();
}
entry.setValue("[" + msg + "]");
trace.add(entry);
throw e;
}
}
// Step #3 increment counters (variable radix increment)
done = incrementVariableRadixCount(counters, boundCoordinates, axisNames);
}
}
catch (RuleStop ignored)
{
// ends this execution cycle
}
catch (RuleJump e)
{
input = e.getCoord();
run = true;
}
}
trace.add(new MapEntry("end: " + getName(), numRulesExec));
ruleInfo.addToRulesExecuted(numRulesExec);
output.put("return", lastExecutedCellValue);
return lastExecutedCellValue;
}