/**
* Copyright 2010 Bing Ran<bing_ran@hotmail.com>
*
* 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 cn.bran.japid.classmeta;
import japa.parser.ast.body.Parameter;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import cn.bran.japid.compiler.JavaSyntaxTool;
import cn.bran.japid.compiler.NamedArgRuntime;
import cn.bran.japid.compiler.Tag;
import cn.bran.japid.compiler.Tag.TagSet;
/**
* the class meta data for templates that are directly renderable, meaning this
* is not for Layout template nor for tag template
*
* special:
* <ul>
* <li>no dobody</li>
* <li>can #{extends 'layout.html'}</li>
* <li>take script params, like in tag template</li>
* <li>the whole body is wrapped as body(), to be called from layout class</li>
* <li>support #{set}</li>
* </ul>
* ,
*
*
*
* @author Bing Ran<bing_ran@hotmail.com>
*
*/
public class TemplateClassMetaData extends AbstractTemplateClassMetaData {
/**
*
*/
private static final String COMMA = ", ";
// there are the "#{set var:val /}
// <methName, methodBody
Map<String, String> setMethods = new HashMap<String, String>();
Map<String, TagSet> setTags = new HashMap<String, TagSet>();
// Experiment: allow any template to be callable as a tag and can handle
// doBody
// null: no doBody, "": there is doBody but no parameters passed back
private String doBodyArgsString;
private char[] doBodyGenericTypeParams = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' };
//
public void addSetTag(String setMethodName, String methodBody, TagSet tag) {
setMethods.put(setMethodName, methodBody);
setTags.put(setMethodName, tag);
}
// for the doBody in the tag template
public void addDoBodyInterface(String bodyArgsString) {
this.doBodyArgsString = bodyArgsString;
}
public void doBody(String tagArgs) {
tagArgs = tagArgs == null ? "" : tagArgs.trim();
this.addDoBodyInterface(tagArgs);
}
// public void addDefTag(String key, String string) {
// // TODO Auto-generated method stub
// }
/**
*
*/
@Override
protected void getterSetter() {
// `set title = "something"
pln();
for (Entry<String, String> en : setMethods.entrySet()) {
String meth = en.getKey();
String setBody = en.getValue();
pln("\t@Override protected void " + meth + "() {");
// local tag defs
// TagSet set = setTags.get(meth);
// for (Tag t: set.tags) {
// declareTagInstance(t);
// }
pln("\t\t" + setBody + ";");
pln("\t}");
}
}
/**
* the main part of the render logic
*/
protected void layoutMethod() {
// doLayout body
pln(TAB + "@Override protected void doLayout() {");
restOfBody();
}
/**
* the entry point of the template: render(...). Concrete views have this
* method while the layouts do not.
*/
protected void renderMethod() {
String resultType = useWithPlay ? RENDER_RESULT : "String";
String paramNameArray = "";
String paramTypeArray = "";
String paramDefaultsArray = "";
String currentClassFQN = (this.packageName == null ? "" : this.packageName + ".") + this.className;
List<Parameter> params = JavaSyntaxTool.parseParams(this.renderArgs);
String renderArgsWithoutAnnos = "";
// / named param stuff
for (Parameter p : params) {
paramNameArray += "\"" + p.getId() + "\", ";
paramTypeArray += "\"" + p.getType() + "\", ";
String defa = JavaSyntaxTool.getDefault(p);
paramDefaultsArray += defa + ",";
renderArgsWithoutAnnos += p.getType() + " " + p.getId() + ",";
}
if (renderArgsWithoutAnnos.endsWith(",")) {
renderArgsWithoutAnnos = renderArgsWithoutAnnos.substring(0, renderArgsWithoutAnnos.length() - 1);
}
String nameParamCode = String.format(NAMED_PARAM_CODE, paramNameArray, paramTypeArray, paramDefaultsArray, currentClassFQN);
pln(nameParamCode);
if (doBodyArgsString != null)
pln(" { setHasDoBody(); }");
if (renderArgs != null) {
for (Parameter p : params) {
addField(p);
}
// set the render(xxx)
if (doBodyArgsString != null) {
pln(String.format(NAMED_PARAM_WITH_BODY, getLineMarker()));
// the template can be called with a callback body
// the field
pln(TAB + "private DoBody body;");
doBodyInterface();
// now the render(...)
pln("\tpublic " + resultType + " render(" + renderArgsWithoutAnnos + ", DoBody body) {");
pln("\t\t" + "this.body = body;");
// assign the params to fields
for (Parameter p : params) {
pln("\t\tthis." + p.getId() + " = " + p.getId() + ";");
}
restOfRenderBody(resultType);
}
// a version without the body part to allow optional body
pln("\tpublic " + resultType + " render(" + renderArgsWithoutAnnos + ") {");
// assign the params to fields
for (Parameter p : params) {
pln("\t\tthis." + p.getId() + " = " + p.getId() + ";");
}
restOfRenderBody(resultType);
} else {
if (doBodyArgsString != null) {
pln(String.format(NAMED_PARAM_WITH_BODY, getLineMarker()));
// the field
pln(TAB + "DoBody body;");
doBodyInterface();
// now the render(...)
pln("\tpublic " + resultType + " render(DoBody body) {");
pln("\t\t" + "this.body = body;");
restOfRenderBody(resultType);
}
// the parameter-less render()
pln("\tpublic " + resultType + " render() {");
restOfRenderBody(resultType);
}
// the static apply method
String args = "";
for (Parameter p : params) {
args += p.getId() + COMMA;
}
if (args.endsWith(COMMA)) {
args = args.substring(0, args.lastIndexOf(COMMA));
}
String applyMethod = isAbstract?
String.format(APPLY_METHOD_ABSTRACT, resultType, renderArgsWithoutAnnos, this.className, args) :
String.format(APPLY_METHOD, resultType, renderArgsWithoutAnnos, this.className, args);
pln("\n" + applyMethod);
}
static final String APPLY_METHOD =
" public static %s apply(%s) {\n" +
" return new %s().render(%s);\n" +
" }\n" ;
static final String APPLY_METHOD_ABSTRACT =
" public static %s apply(%s) {\n" +
" throw new RuntimeException(\"Cannot run an Japid template annotated as abstract.\");\n" +
" }\n" ;
private void restOfRenderBody(String resultType) {
if (stopWatch)
pln("\t\tsetStopwatchOn();");
// pln("\t\tstartRendering(); ");
pln("\t\ttry {super.layout(" + superClassRenderArgs + ");} catch (RuntimeException __e) { super.handleException(__e);} " + getLineMarker());
if (useWithPlay) {
pln("\t\treturn getRenderResult();");
} else {
pln("\t\treturn getRenderResult().toString();");
}
pln("\t}");
}
private void doBodyInterface() {
// let do the doDody callback interface
// doBody interface:
if (doBodyArgsString != null) {
List<String> args = JavaSyntaxTool.parseArgs(doBodyArgsString);
String genericTypeList = "";
String renderArgList = "";
int i = 0;
for (String arg : args) {
char c = doBodyGenericTypeParams[i++];
genericTypeList += "," + c;
renderArgList += "," + c + " " + Character.toLowerCase(c);
}
if (genericTypeList.startsWith(",")) {
// remove the first comma
genericTypeList = "<" + genericTypeList.substring(1) + ">";
renderArgList = renderArgList.substring(1);
}
pln("public static interface DoBody", genericTypeList, " {");
pln(" void render(" + renderArgList + ");");
pln(" void setBuffer(StringBuilder sb);\n" + " void resetBuffer();\n" + "}");
// add a convenient method to get the render result from the doBody object
String renderArgs = renderArgList.replaceAll("[A-Z]", "");
pln(genericTypeList, " String renderBody(" + renderArgList + ") {\n" +
" StringBuilder sb = new StringBuilder();\n" +
" if (body != null){\n" +
" body.setBuffer(sb);\n" +
" body.render(" + renderArgs + ");\n" +
" body.resetBuffer();\n" +
" }\n" +
" return sb.toString();\n" +
" }");
}
}
@Override
void childLayout() {
// concrete views do not have this
}
protected static final String NAMED_PARAM_CODE = "" +
"/* based on https://github.com/branaway/Japid/issues/12\n" +
" */\n" +
"\tpublic static final String[] argNames = new String[] {/* args of the template*/%s };\n" +
"\tpublic static final String[] argTypes = new String[] {/* arg types of the template*/%s };\n" +
"\tpublic static final Object[] argDefaults= new Object[] {%s };\n" +
"\tpublic static java.lang.reflect.Method renderMethod = getRenderMethod(%s.class);\n\n" +
"\t{\n" +
"\t\tsetRenderMethod(renderMethod);\n" +
"\t\tsetArgNames(argNames);\n" +
"\t\tsetArgTypes(argTypes);\n" +
"\t\tsetArgDefaults(argDefaults);\n" +
"\t\tsetSourceTemplate(sourceTemplate);\n" +
"\t}\n" +
"" +
"////// end of named args stuff\n";
protected static final String NAMED_PARAM_WITH_BODY = "public cn.bran.japid.template.RenderResult render(DoBody body, cn.bran.japid.compiler.NamedArgRuntime... named) {\n"
+ " Object[] args = buildArgs(named, body);\n"
+ " try {return runRenderer(args);} catch(RuntimeException e) {handleException(e); throw e;} %s\n"
+ "}\n";
/*
* (non-Javadoc)
*
* @see
* cn.bran.japid.classmeta.AbstractTemplateClassMetaData#merge(cn.bran.japid
* .classmeta.AbstractTemplateClassMetaData)
*/
@Override
public void merge(AbstractTemplateClassMetaData a) {
super.merge(a);
if (a instanceof TemplateClassMetaData) {
TemplateClassMetaData b = (TemplateClassMetaData) a;
this.setMethods.putAll(b.setMethods);
this.setTags.putAll(b.setTags);
}
else {
throw new RuntimeException("cannot merge metadata from another type to AbstractTemplateClassMetaData: " + a.className);
}
}
}