RenderState.java
package org.klojang.templates;
import org.klojang.check.Check;
import org.klojang.path.Path;
import org.klojang.util.collection.IntList;
import java.util.*;
import static java.util.stream.Collectors.toList;
import static org.klojang.check.CommonChecks.*;
import static org.klojang.templates.RenderErrorCode.*;
import static org.klojang.templates.TemplateUtils.getFQN;
import static org.klojang.util.ObjectMethods.ifNotNull;
import static org.klojang.util.ObjectMethods.n2e;
final class RenderState {
private static final SoloSession[] ZERO_SESSIONS = new SoloSession[0];
private final SessionConfig config;
// variables that have not been set yet
private final Set<String> todo;
private final Map<Template, SessionData> children;
// variable occurrence values. A variable may occur multiple times
// within the same template, and occurrences may end up having
// different values due to being escaped differently. The keys in
// the map are the indices of VarPart parts.
private final Map<Integer, Object> varValues;
RenderState(SessionConfig config) {
this.config = config;
int sz = config.template().countNestedTemplates();
this.children = new IdentityHashMap<>(sz);
sz = config.template().countVariableOccurrences();
this.varValues = HashMap.newHashMap(sz);
this.todo = new HashSet<>(config.template().getVariables());
}
SessionConfig getSessionConfig() {
return config;
}
SessionData getSessionData(Template tmpl) {
return children.get(tmpl);
}
SoloSession[] createChildSessions(Template t, String separator, int repeats) {
SoloSession[] sessions;
if (repeats == 0) {
sessions = ZERO_SESSIONS;
} else {
sessions = new SoloSession[repeats];
for (int i = 0; i < repeats; ++i) {
sessions[i] = config.newChildSession(t);
}
}
this.children.put(t, new SessionData(sessions, n2e(separator)));
return sessions;
}
SoloSession[] getOrCreateChildSessions(Template t, String separator, int repeats) {
SessionData children = this.children.get(t);
if (children == null) {
return createChildSessions(t, separator, repeats);
} else if (children.sessions().length == repeats) {
return children.sessions();
}
throw REPETITION_MISMATCH.getException(
getFQN(t),
children.sessions().length,
repeats);
}
boolean isProcessed(Template template) {
return children.get(template) != null;
}
boolean isDisabled(Template template) {
SessionData sd = children.get(template);
return sd != null && sd.sessions().length == 0;
}
SoloSession[] getChildSessions(Template template) {
return ifNotNull(children.get(template), SessionData::sessions);
}
Object getVar(int partIndex) {
return varValues.get(partIndex);
}
void setVar(int partIndex, Object value) {
varValues.put(partIndex, value);
}
void done(String var) {
todo.remove(var);
}
List<String> todo() {
return List.copyOf(todo);
}
List<String> getAllUnsetVariables(boolean relative) {
if (relative) {
ArrayList<Path> paths = new ArrayList<>();
collectUnsetVariables(this, paths, Path.empty());
return paths.stream().map(Path::toString).collect(toList());
}
ArrayList<String> vars = new ArrayList<>();
collectUnsetVariables(this, vars);
return vars;
}
// collects absolute paths
private static void collectUnsetVariables(RenderState state, ArrayList<String> vars) {
Template myTmpl = state.config.template();
state.todo.stream().map(var -> getFQN(myTmpl, var)).forEach(vars::add);
myTmpl.getNestedTemplates().forEach(t -> {
SessionData children = state.children.get(t);
if (children == null) {
TemplateUtils.collectFQNs(t, vars);
} else if (children.sessions().length > 0) {
collectUnsetVariables(children.sessions()[0].state(), vars);
}
});
}
// collects relative paths
private static void collectUnsetVariables(
RenderState state,
ArrayList<Path> vars,
Path path) {
state.todo.stream().map(path::append).forEach(vars::add);
Template tmpl = state.config.template();
tmpl.getNestedTemplates().forEach(t -> {
Path next = path.append(t.getName());
SessionData sd = state.children.get(t);
if (sd == null) {
TemplateUtils.collectFQNs(t, vars, next);
} else if (sd.sessions().length > 0) {
collectUnsetVariables(sd.sessions()[0].state(), vars, next);
}
});
}
boolean ready() {
return ready(this);
}
private static boolean ready(RenderState state) {
if (state.todo.isEmpty()) {
for (Template t : state.config.template().getNestedTemplates()) {
SessionData sd = state.children.get(t);
if (sd == null) {
if (t.hasVariables()) {
return false;
}
} else if (sd.sessions().length > 0 && !ready(sd.sessions()[0].state())) {
return false;
}
}
return true;
}
return false;
}
void unset(Path path) {
unset(this, path);
}
private static void unset(RenderState state, Path path) {
String name = path.segment(0);
if (path.size() == 1) {
IntList occurrences = state.config.template().variables().get(name);
Check.that(occurrences).is(notNull(),
NO_SUCH_VARIABLE.getExceptionSupplier(name));
state.todo.add(name);
occurrences.stream().forEach(state.varValues.keySet()::remove);
} else {
Template tmpl = state.config.template();
Check.that(name).is(in(), tmpl.getNestedTemplateNames(),
NO_SUCH_TEMPLATE.getExceptionSupplier(getFQN(tmpl, name)));
Template nested = tmpl.getNestedTemplate(name);
SessionData children = state.children.get(nested);
if (children != null) {
Arrays.stream(children.sessions()).forEach(s -> unset(s.state(), path.shift()));
}
}
}
void clear(Template tmpl) {
Arrays.stream(children.get(tmpl).sessions()).forEach(this::clear);
children.remove(tmpl);
}
private void clear(SoloSession session) {
RenderState state = session.state();
state.varValues.clear();
state.todo.addAll(state.config.template().getVariables());
state.children.values()
.stream()
.map(SessionData::sessions)
.flatMap(Arrays::stream)
.forEach(this::clear);
state.children.clear();
}
boolean isSet(Path path) {
return isSet(this, path);
}
private static boolean isSet(RenderState state, Path path) {
String name = path.segment(0);
if (path.size() == 1) {
if (state.todo.contains(name)) {
return false;
}
Template tmpl = state.config.template();
Check.that(name).is(keyIn(), tmpl.variables(),
NO_SUCH_VARIABLE.getExceptionSupplier(getFQN(tmpl, name)));
return true;
}
Template tmpl = state.config.template();
Check.that(name).is(in(), tmpl.getNestedTemplateNames(),
NO_SUCH_TEMPLATE.getExceptionSupplier(getFQN(tmpl, name)));
Template nested = tmpl.getNestedTemplate(name);
SessionData children = state.children.get(nested);
if (children == null) {
return false;
} else if (children.sessions().length == 0) {
return true;
}
return isSet(children.sessions()[0].state(), path.shift());
}
}