package org.webpieces.templating.impl;
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.webpieces.ctx.api.Current;
import org.webpieces.templating.api.ClosureUtil;
import org.webpieces.templating.api.HtmlTag;
import org.webpieces.templating.api.HtmlTagLookup;
import org.webpieces.templating.api.RouterLookup;
import org.webpieces.templating.impl.html.EscapeHTMLFormatter;
import org.webpieces.templating.impl.html.NullFormatter;
import groovy.lang.Closure;
import groovy.lang.MissingPropertyException;
import groovy.lang.Script;
public abstract class GroovyTemplateSuperclass extends Script {
public static String OUT_PROPERTY_NAME = "__out";
public static final EscapeHTMLFormatter ESCAPE_HTML_FORMATTER = new EscapeHTMLFormatter();
private static final NullFormatter NULL_FORMATTER = new NullFormatter();
private EscapeCharactersFormatter formatter;
private HtmlTagLookup tagLookup;
private Map<Object, Object> setTagProperties;
private String superTemplateFilePath;
private RouterLookup urlLookup;
private ThreadLocal<String> sourceLocal = new ThreadLocal<>();
public void initialize(EscapeCharactersFormatter f, HtmlTagLookup tagLookup,
Map<Object, Object> setTagProps, RouterLookup urlLookup) {
formatter = f;
this.tagLookup = tagLookup;
this.setTagProperties = setTagProps;
this.urlLookup = urlLookup;
}
//TODO: Make formatter a ThreadLocal<Stack> such that if you nest verbatims with tags
//then uninstallNullFormatter only means pop the stack such that you are still in verbatim
//ie. this won't work well with nested verbatim right now
protected void installNullFormatter() {
formatter = NULL_FORMATTER;
}
protected void installHtmlFormatter() {
formatter = ESCAPE_HTML_FORMATTER;
}
public String useFormatter(Object val) {
return formatter.format(val);
}
protected void runTag(String tagName, Map<Object, Object> args, Closure<?> closure, String srcLocation) {
srcLocation = modifySourceLocation2(srcLocation);
HtmlTag tag = tagLookup.lookup(tagName);
PrintWriter writer = (PrintWriter) getProperty(OUT_PROPERTY_NAME);
try {
tag.runTag(args, closure, writer, this, srcLocation);
} catch(Exception e) {
throw new RuntimeException("Error running tag #{"+tagName+"}#. See exception cause below."+srcLocation, e);
}
}
protected String runClosure(String tagName, Closure<?> closure, String srcLocation) {
srcLocation = modifySourceLocation2(srcLocation);
try {
return ClosureUtil.toString(tagName, closure, null);
} catch(Exception e) {
throw new RuntimeException("Error running tag #{"+tagName+"}#. See exception cause below."+srcLocation, e);
}
}
public void putSetTagProperty(Object key, Object val) {
setTagProperties.put(key, val);
}
public Object getSetTagProperty(Object key) {
return setTagProperties.get(key);
}
public void setSuperTemplateFilePath(String path) {
this.superTemplateFilePath = path;
}
public String getSuperTemplateFilePath() {
return superTemplateFilePath;
}
public Map<Object, Object> getSetTagProperties() {
return setTagProperties;
}
public String fetchUrl(String routeId, Map<?, ?> args, String srcLocation) {
srcLocation = modifySourceLocation2(srcLocation);
try {
Map<String, String> urlParams = new HashMap<>();
for(Map.Entry<?, ?> entry : args.entrySet()) {
String key = entry.getKey().toString();
Object value = entry.getValue();
if(value == null)
throw new IllegalArgumentException("Cannot use null param in a url for param name="+key);
//urlencoding happens for each value in the router...
urlParams.put(key, value.toString());
}
return urlLookup.fetchUrl(routeId, urlParams);
} catch(Exception e) {
throw new RuntimeException("Error fetching route='"+routeId+"' ("+e.getMessage()+"). "+srcLocation+"\nThe list of params fed from html file="+args, e);
}
}
public Object optional(String property) {
try {
return super.getProperty(property);
} catch(MissingPropertyException e) {
return null;
}
}
@Override
public void setProperty(String property, Object value) {
super.setProperty(property, value);
}
@Override
public Object getProperty(String property) {
String srcLocation = modifySourceLocation2(sourceLocal.get());
try {
return super.getProperty(property);
} catch (MissingPropertyException e) {
throw new IllegalArgumentException("No such property '"+property+"' but perhaps you forgot quotes "
+ "around it or you forgot to pass it in from the controller's return value(with the RouteId) OR "
+ "lastly, if this is inside a custom tag, perhaps the tag did not pass in the correct arguments."+srcLocation, e);
}
}
public String getMessage(Object ... args) {
String srcLocation = modifySourceLocation2(sourceLocal.get());
if(args.length < 2)
throw new IllegalArgumentException("&{...}& must include two arguments separated by a comma and did not."+srcLocation);
else if(args[1] == null)
throw new IllegalArgumentException("The second argument in &{...}& evaluated to null...did you forget quotes or "
+ "you forgot to pass in a valid value to that variable?"+srcLocation);
String defaultText = "";
if(args[0] != null)
defaultText = args[0].toString();
String key = args[1].toString();
Object[] newArgs = createArgsArray(args);
String msg = getMessageImpl(defaultText, key);
String result = MessageFormat.format(msg, newArgs);
return result;
}
private Object[] createArgsArray(Object... args) {
List<Object> argsList = new ArrayList<>();
for(int i = 2; i < args.length; i++) {
argsList.add(args[i]);
}
Object[] newArgs = argsList.toArray();
return newArgs;
}
private String getMessageImpl(String defaultText, String key) {
List<Locale> locales = Current.request().preferredLocales;
for(Locale l : locales) {
String message = Current.messages().get(key, l);
if(message != null)
return message;
}
return defaultText;
}
//We should probably turn this into a ThreadLocal<Stack> ...
public void enterExpression(String srcLocation) {
sourceLocal.set(srcLocation);
}
public void exitExpression() {
sourceLocal.set(null);
}
public static String modifySourceLocation2(String srcLocation) {
return "\n\n\t"+srcLocation+"\n";
}
}