package ca.uhn.fhir.narrative.template.tags;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import ca.uhn.fhir.narrative.template.nodes.BlockNode;
import ca.uhn.fhir.narrative.template.nodes.LNode;
class For extends Tag {
private static final String OFFSET = "offset";
private static final String LIMIT = "limit";
private static final String CONTINUE = "continue";
/*
* forloop.length # => length of the entire for loop
* forloop.index # => index of the current iteration
* forloop.index0 # => index of the current iteration (zero based)
* forloop.rindex # => how many items are still left?
* forloop.rindex0 # => how many items are still left? (zero based)
* forloop.first # => is this the first iteration?
* forloop.last # => is this the last iteration?
*/
private static final String FORLOOP = "forloop";
private static final String LENGTH = "length";
private static final String INDEX = "index";
private static final String INDEX0 = "index0";
private static final String RINDEX = "rindex";
private static final String RINDEX0 = "rindex0";
private static final String FIRST = "first";
private static final String LAST = "last";
private static final String NAME = "name";
/*
* For loop
*/
@Override
public Object render(Map<String, Object> context, LNode... nodes) {
// The first node in the array denotes whether this is a for-tag
// over an array, `for item in array ...`, or a for-tag over a
// range, `for i in (4..item.length)`.
boolean array = super.asBoolean(nodes[0].render(context));
String id = super.asString(nodes[1].render(context));
context.put(FORLOOP, new HashMap<String, Object>());
Object rendered = array ? renderArray(id, context, nodes) : renderRange(id, context, nodes);
context.remove(FORLOOP);
return rendered;
}
private Object renderArray(String id, Map<String, Object> context, LNode... tokens) {
StringBuilder builder = new StringBuilder();
// attributes start from index 5
Map<String, Integer> attributes = getAttributes(5, context, tokens);
int offset = attributes.get(OFFSET);
int limit = attributes.get(LIMIT);
Object[] array = super.asArray(tokens[2].render(context));
LNode block = tokens[3];
LNode blockIfEmptyOrNull = tokens[4];
if(array == null || array.length == 0) {
return blockIfEmptyOrNull == null ? null : blockIfEmptyOrNull.render(context);
}
int length = Math.min(limit, array.length);
Map<String, Object> forLoopMap = (Map<String, Object>)context.get(FORLOOP);
forLoopMap.put(NAME, id + "-" + tokens[2].toString());
int continueIndex = offset;
for (int i = offset, n = 0; n < limit && i < array.length; i++, n++) {
continueIndex = i;
boolean first = (i == offset);
boolean last = ((n == limit - 1) || (i == array.length - 1));
context.put(id, array[i]);
forLoopMap.put(LENGTH, length);
forLoopMap.put(INDEX, n + 1);
forLoopMap.put(INDEX0, n);
forLoopMap.put(RINDEX, length - n);
forLoopMap.put(RINDEX0, length - n - 1);
forLoopMap.put(FIRST, first);
forLoopMap.put(LAST, last);
List<LNode> children = ((BlockNode)block).getChildren();
boolean isBreak = false;
for (LNode node : children) {
Object value = node.render(context);
if(value == Tag.Statement.CONTINUE) {
// break from this inner loop: equals continue outer loop!
break;
}
if(value == Tag.Statement.BREAK) {
// break from inner loop
isBreak = true;
break;
}
if (value != null && value.getClass().isArray()) {
Object[] arr = (Object[]) value;
for (Object obj : arr) {
builder.append(String.valueOf(obj));
}
}
else {
builder.append(super.asString(value));
}
}
if(isBreak) {
// break from outer loop
break;
}
}
context.put(CONTINUE, continueIndex + 1);
return builder.toString();
}
private Object renderRange(String id, Map<String, Object> context, LNode... tokens) {
StringBuilder builder = new StringBuilder();
// attributes start from index 5
Map<String, Integer> attributes = getAttributes(5, context, tokens);
int offset = attributes.get(OFFSET);
int limit = attributes.get(LIMIT);
LNode block = tokens[4];
try {
int from = super.asNumber(tokens[2].render(context)).intValue();
int to = super.asNumber(tokens[3].render(context)).intValue();
int length = (to - from);
Map<String, Object> forLoopMap = (Map<String, Object>)context.get(FORLOOP);
int continueIndex = from + offset;
for (int i = from + offset, n = 0; i <= to && n < limit; i++, n++) {
continueIndex = i;
boolean first = (i == (from + offset));
boolean last = ((i == to) || (n == limit - 1));
context.put(id, i);
forLoopMap.put(LENGTH, length);
forLoopMap.put(INDEX, n + 1);
forLoopMap.put(INDEX0, n);
forLoopMap.put(RINDEX, length - n);
forLoopMap.put(RINDEX0, length - n - 1);
forLoopMap.put(FIRST, first);
forLoopMap.put(LAST, last);
List<LNode> children = ((BlockNode)block).getChildren();
boolean isBreak = false;
for (LNode node : children) {
Object value = node.render(context);
if(value == null) {
continue;
}
if(value == Tag.Statement.CONTINUE) {
// break from this inner loop: equals continue outer loop!
break;
}
if(value == Tag.Statement.BREAK) {
// break from inner loop
isBreak = true;
break;
}
if(super.isArray(value)) {
Object[] arr = super.asArray(value);
for (Object obj : arr) {
builder.append(String.valueOf(obj));
}
}
else {
builder.append(super.asString(value));
}
}
if(isBreak) {
// break from outer loop
break;
}
}
context.put(CONTINUE, continueIndex + 1);
}
catch (Exception e) {
/* just ignore incorrect expressions */
}
return builder.toString();
}
private Map<String, Integer> getAttributes(int fromIndex, Map<String, Object> context, LNode... tokens) {
Map<String, Integer> attributes = new HashMap<String, Integer>();
attributes.put(OFFSET, 0);
attributes.put(LIMIT, Integer.MAX_VALUE);
for (int i = fromIndex; i < tokens.length; i++) {
Object[] attribute = super.asArray(tokens[i].render(context));
try {
attributes.put(super.asString(attribute[0]), super.asNumber(attribute[1]).intValue());
}
catch (Exception e) {
/* just ignore incorrect attributes */
}
}
return attributes;
}
}