/*
* Copyright 2016 Martin Kouba
*
* 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.trimou.handlebars;
import static org.trimou.handlebars.HelperValidator.newValidationException;
import static org.trimou.handlebars.Helpers.initIntHashEntry;
import static org.trimou.handlebars.Helpers.isValuePlaceholder;
import static org.trimou.handlebars.OptionsHashKeys.LIMIT;
import static org.trimou.handlebars.OptionsHashKeys.TIMES;
import static org.trimou.handlebars.OptionsHashKeys.WHILE;
import java.util.ListIterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.trimou.exception.MustacheException;
import org.trimou.exception.MustacheProblem;
import org.trimou.handlebars.HelperDefinition.ValuePlaceholder;
import org.trimou.util.Checker;
import org.trimou.util.ImmutableSet;
/**
* Allows to repeat the section multiple times:
*
* <pre>
* {{#repeat times=3}}
* Hello three times!
* {{/repeat}}
* </pre>
*
* <p>
* Or until the {@code while} expression evaluates to a "falsy" value:
* </p>
*
* <pre>
* {{#with items.iterator}}
* {{#repeat while=hasNext}}
* {{next}}
* {{/repeat}}
* {{/with}}
* </pre>
*
* <p>
* An interesting use case might be using {@link ListIterator} to iterate over the list in reverse order:
* </p>
*
* <pre>
* {{#invoke items.size on=items m="listIterator"}}
* {{#repeat while=hasPrevious}}
* {{previous}}
* {{/repeat}}
* {{/invoke}}
* </pre>
*
* @author Martin Kouba
* @see Checker#isFalsy(Object)
* @since 2.0
*/
public class RepeatHelper extends BasicSectionHelper {
private final ConcurrentMap<String, String> whilePlaceholderKeys;
public RepeatHelper() {
this.whilePlaceholderKeys = new ConcurrentHashMap<>();
}
@Override
public void execute(Options options) {
Integer times = initIntHashEntry(options, TIMES);
if (times != null) {
for (int i = 0; i < times; i++) {
options.fn();
}
} else {
Object whileValue = options.getHash().get(WHILE);
if (whileValue != null) {
Object value;
String whileExpr = whilePlaceholderKeys.get(getKey(options));
if (whileExpr != null) {
value = whileValue;
} else {
whileExpr = whileValue.toString();
value = options.getValue(whileExpr);
}
int limit = initIntHashEntry(options, LIMIT,
Integer.MAX_VALUE);
int i = 0;
while (!Checker.isFalsy(value)) {
options.fn();
if (i++ >= limit) {
throw new MustacheException(
MustacheProblem.RENDER_GENERIC_ERROR,
"Iteration limit exceeded");
}
value = options.getValue(whileExpr);
}
}
}
}
@Override
public void validate(HelperDefinition definition) {
super.validate(definition);
if (definition.getHash().containsKey(TIMES)) {
Object times = definition.getHash().get(TIMES);
if (!isValuePlaceholder(times) && !(times instanceof Integer)) {
try {
Integer.valueOf(times.toString());
} catch (NumberFormatException e) {
throw new MustacheException(
MustacheProblem.COMPILE_HELPER_VALIDATION_FAILURE,
e);
}
}
} else if (definition.getHash().containsKey(WHILE)) {
Object whileExpr = definition.getHash().get(WHILE);
if (isValuePlaceholder(whileExpr)) {
whilePlaceholderKeys.put(getKey(definition),
((ValuePlaceholder) whileExpr).getName());
}
} else {
throw newValidationException(
"Either 'times' or 'while' hash entry is expected",
RepeatHelper.class, definition);
}
}
@Override
protected int numberOfRequiredParameters() {
return 0;
}
@Override
protected int numberOfRequiredHashEntries() {
return 1;
}
@Override
protected Set<String> getSupportedHashKeys() {
return ImmutableSet.of(TIMES, WHILE, LIMIT);
}
private String getKey(HelperDefinition definition) {
return definition.getTagInfo().getTemplateGeneratedId()
+ definition.getTagInfo().getId();
}
}