package com.google.sitebricks.rendering.control;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.sitebricks.Renderable;
import com.google.sitebricks.Respond;
import com.google.sitebricks.compiler.EvaluatorCompiler;
import com.google.sitebricks.compiler.ExpressionCompileException;
import com.google.sitebricks.compiler.Token;
import com.google.sitebricks.headless.Request;
import com.google.sitebricks.rendering.Attributes;
import com.google.sitebricks.rendering.SelfRendering;
import net.jcip.annotations.ThreadSafe;
import java.util.*;
/**
* <p> Widget renders an XML-like tag </p>
*
* @author Dhanji R. Prasanna (dhanji@gmail.com)
*/
@ThreadSafe
@SelfRendering
class XmlWidget implements Renderable {
private final WidgetChain widgetChain;
private final boolean selfClosed;
private final String name;
private final Map<String, List<Token>> attributes;
// HACK Extremely ouch! Replace with Assisted inject.
private static volatile Provider<Request> request;
private static final Set<String> CONTEXTUAL_ATTRIBS;
static {
Set<String> set = new HashSet<String>();
set.add("href");
set.add("action");
set.add("src");
CONTEXTUAL_ATTRIBS = Collections.unmodifiableSet(set);
}
XmlWidget(WidgetChain widgetChain, String name, EvaluatorCompiler compiler,
@Attributes Map<String, String> attributes) throws ExpressionCompileException {
this.widgetChain = widgetChain;
this.name = name;
this.attributes = Collections.unmodifiableMap(compile(attributes, compiler));
//hacky. Script tags should not be self-closed due to IE insanity.
this.selfClosed =
widgetChain instanceof TerminalWidgetChain && !"script".equalsIgnoreCase(name);
}
//compiles a map of name:value attrs into a map of name:token renderables
static Map<String, List<Token>> compile(Map<String, String> attributes,
EvaluatorCompiler compiler)
throws ExpressionCompileException {
Map<String, List<Token>> map = new LinkedHashMap<String, List<Token>>();
for (Map.Entry<String, String> attribute : attributes.entrySet()) {
map.put(attribute.getKey(), compiler.tokenizeAndCompile(attribute.getValue()));
}
return map;
}
public void render(Object bound, Respond respond) {
writeOpenTag(bound, respond, name, attributes);
//write children
if (selfClosed) {
respond.write("/>"); //write self-closed tag
} else {
respond.write('>');
widgetChain.render(bound, respond);
//close tag
respond.write("</");
respond.write(name);
respond.write('>');
}
}
static void writeOpenTag(Object bound, Respond respond, String name,
Map<String, List<Token>> attributes) {
respond.write('<');
respond.write(name);
respond.write(' ');
//write attributes
for (Map.Entry<String, List<Token>> attribute : attributes.entrySet()) {
respond.write(attribute.getKey());
respond.write("=\"");
final List<Token> tokenList = attribute.getValue();
for (int i = 0; i < tokenList.size(); i++) {
Token token = tokenList.get(i);
if (token.isExpression()) {
respond.write(token.render(bound));
} else {
respond.write(
contextualizeIfNeeded(attribute.getKey(), (0 == i), token.render(bound)));
}
}
respond.write("\" ");
}
respond.chew();
}
static String contextualizeIfNeeded(String attribute, boolean isFirstToken, String raw) {
if (isFirstToken && CONTEXTUAL_ATTRIBS.contains(attribute)) {
//add context to path if needed
if (raw.startsWith("/")) {
if (!raw.startsWith("//")) // Ignore protocol-relative paths.
raw = request.get().context() + raw;
}
}
return raw;
}
public <T extends Renderable> Set<T> collect(Class<T> clazz) {
return widgetChain.collect(clazz);
}
@Inject
public void setRequestProvider(Provider<Request> requestProvider) {
XmlWidget.request = requestProvider;
}
}