package org.apache.slide.projector.processor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.slide.projector.ConfigurableProcessor;
import org.apache.slide.projector.ConfigurationException;
import org.apache.slide.projector.ContentType;
import org.apache.slide.projector.Context;
import org.apache.slide.projector.ProcessException;
import org.apache.slide.projector.Result;
import org.apache.slide.projector.descriptor.AnyValueDescriptor;
import org.apache.slide.projector.descriptor.ParameterDescriptor;
import org.apache.slide.projector.descriptor.ResultDescriptor;
import org.apache.slide.projector.descriptor.ResultEntryDescriptor;
import org.apache.slide.projector.descriptor.StateDescriptor;
import org.apache.slide.projector.descriptor.StringValueDescriptor;
import org.apache.slide.projector.i18n.DefaultMessage;
import org.apache.slide.projector.i18n.ErrorMessage;
import org.apache.slide.projector.i18n.ParameterMessage;
import org.apache.slide.projector.util.StreamHelper;
import org.apache.slide.projector.value.ArrayValue;
import org.apache.slide.projector.value.NullValue;
import org.apache.slide.projector.value.PrintableValue;
import org.apache.slide.projector.value.StreamableValue;
import org.apache.slide.projector.value.StringValue;
import org.apache.slide.projector.value.Value;
public class TemplateRenderer implements ConfigurableProcessor {
private static Logger logger = Logger.getLogger(TemplateRenderer.class.getName());
public final static String OK = "ok";
public final static String OUTPUT = "output";
public final static String FRAGMENT = "fragment";
protected final static String FRAGMENT_START = "<!--*** Start of '";
protected final static String FRAGMENT_END = "<!--*** End of '";
protected final static String FRAGMENT_CLOSE = "' ***-->";
protected final static String IGNORE_START = "<!--*** Start ignore ***-->";
protected final static String IGNORE_END = "<!-- *** End ignore *** -->";
protected final static String TAG_OPEN = "<%";
protected final static String TAG_CLOSE = "%>";
protected final static String CONDITION_OPEN = "<?";
protected final static String CONDITION_CLOSE = "?>";
protected final static char SEPARATOR = ';';
protected final static String OPTIONAL = "optional";
protected final static String REQUIRED = "required";
protected final static String DEFAULT_FRAGMENT = "-default-fragment-";
protected final static ResultDescriptor resultDescriptor = new ResultDescriptor(
new StateDescriptor[] { StateDescriptor.OK_DESCRIPTOR },
new ResultEntryDescriptor[]{
new ResultEntryDescriptor(OUTPUT, new DefaultMessage("templateRenderer/result/output"), "*", true)
});
private ParameterDescriptor[] parameterDescriptors;
protected List parameterDescriptions;
protected boolean fragments;
private Map templates;
private String[] requiredFragments, optionalFragments;
private boolean ignoreUndefinedFragments = true;
protected List optionalParameters = new ArrayList(); // all variables used in conditions reflect optional parameters
// FIXME: Nested fragments
public void configure(StreamableValue config) throws ConfigurationException {
templates = new HashMap();
parameterDescriptions = new ArrayList();
try {
String template = StreamHelper.streamToString(config);
// ignore sections marked as ignored
int currentPosition = 0;
int lastPosition = 0;
StringBuffer strippedTemplate = new StringBuffer(template.length());
while ((currentPosition = template.indexOf(IGNORE_START, currentPosition)) >= 0) {
strippedTemplate.append(template.substring(lastPosition, currentPosition));
currentPosition = template.indexOf(IGNORE_END, currentPosition)+IGNORE_END.length();
lastPosition = currentPosition;
}
strippedTemplate.append(template.substring(lastPosition));
template = strippedTemplate.toString();
// split template into different fragments if fragment identifiers are found
currentPosition = 0;
boolean fragments = false;
while ((currentPosition = template.indexOf(FRAGMENT_START, currentPosition)) >= 0) {
fragments = true;
currentPosition += FRAGMENT_START.length();
int startTagClose = template.indexOf(FRAGMENT_CLOSE, currentPosition);
String fragmentName = template.substring(currentPosition, startTagClose);
currentPosition = startTagClose+FRAGMENT_CLOSE.length();
int fragmentEnd = template.indexOf(FRAGMENT_END, currentPosition);
if ( !ignoreFragment(fragmentName) ) {
Template fragment = new Template(template.substring(currentPosition, fragmentEnd));
templates.put(fragmentName, fragment);
}
currentPosition = fragmentEnd+FRAGMENT_CLOSE.length();
}
// check if all required templates are defined
if (requiredFragments != null ) {
for ( int i = 0; i < requiredFragments.length; i++ ) {
boolean requiredFragmentDefined = false;
for ( Iterator j = templates.keySet().iterator(); j.hasNext(); ) {
String definedFragment = (String)j.next();
if ( definedFragment.startsWith(requiredFragments[i]) ) {
requiredFragmentDefined = true;
break;
}
}
if ( !requiredFragmentDefined ) {
throw new ConfigurationException(new ErrorMessage("templateRenderer/requiredFragmentMissing", new String[] { requiredFragments[i] }));
}
}
}
parameterDescriptions.add(new ParameterDescriptor(FRAGMENT, new ParameterMessage("templateRenderer/fragment"), new StringValueDescriptor((String [])templates.keySet().toArray(new String[0])), new StringValue(DEFAULT_FRAGMENT)));
if ( fragments ) {
if ( requiredFragments != null ) {
templates.put(DEFAULT_FRAGMENT, templates.get(requiredFragments[0]));
} else if ( optionalFragments != null ) {
templates.put(DEFAULT_FRAGMENT, templates.get(optionalFragments[0]));
}
} else {
templates.put(DEFAULT_FRAGMENT, new Template(template));
}
parameterDescriptors = (ParameterDescriptor [])parameterDescriptions.toArray(new ParameterDescriptor[parameterDescriptions.size()]);
} catch (IOException ioexception) {
logger.log(Level.SEVERE, "Could not load configuration resource!");
}
}
public Result process(Map parameter, Context context) throws Exception {
String fragment = ((StringValue)parameter.get(FRAGMENT)).toString();
Template template = getRequiredFragment(fragment);
StringBuffer buffer = new StringBuffer(template.getLength());
template.evaluate(buffer, parameter);
return new Result(OK, OUTPUT, new StringValue(buffer.toString(), template.getContentType(), template.isDocument() ));
}
public void setRequiredFragments(String[] requiredFragments) {
this.requiredFragments = requiredFragments;
}
public void setOptionalFragments(String[] optionalFragments) {
this.optionalFragments = optionalFragments;
}
public void ignoreUndefinedFragments(boolean ignoreUndefinedFragments) {
this.ignoreUndefinedFragments = ignoreUndefinedFragments;
}
public StringValue renderFragment(String fragment, Map parameter) throws ProcessException {
Template template = getRequiredFragment(fragment);
return renderFragment(template, parameter);
}
public StringValue renderFragment(Template template, Map parameter) throws ProcessException {
StringBuffer buffer = new StringBuffer(template.getLength());
template.evaluate(buffer, parameter);
return new StringValue(buffer.toString());
}
public void renderFragment(StringBuffer buffer, String fragment, Map parameter) throws ProcessException {
Template template = getRequiredFragment(fragment);
template.evaluate(buffer, parameter);
}
protected Template getRequiredFragment(String fragment) throws ProcessException {
Template template = (Template)templates.get(fragment);
if ( template == null ) throw new ProcessException(new ErrorMessage("templateArrayRenderer/fragmentNotFound", new String[] { fragment }));
return template;
}
protected Template getOptionalFragment(String fragment) {
return (Template)templates.get(fragment);
}
protected Template getOptionalFragment(String fragment, Template defaultTemplate) {
Template optionalFragment = (Template)templates.get(fragment);
if ( optionalFragment == null ) return defaultTemplate;
return optionalFragment;
}
public ParameterDescriptor[] getParameterDescriptors() {
return parameterDescriptors;
}
public ResultDescriptor getResultDescriptor() {
return resultDescriptor;
}
protected String[] getDefinedFragments() {
return (String [])templates.keySet().toArray(new String[templates.size()]);
}
protected List getTemplateParameterDescriptor(String[] fragments) {
List templateParameterDescriptors = new ArrayList();
for ( int i = 0; i < fragments.length; i++ ) {
Template template = (Template)templates.get(fragments[i]);
if ( template != null ) {
templateParameterDescriptors.addAll(template.getTemplateParameterDescriptors().values());
}
}
return templateParameterDescriptors;
}
protected boolean ignoreFragment(String fragmentName) {
if ( requiredFragments == null && optionalFragments == null ) return false;
if ( requiredFragments != null ) {
for ( int i = 0; i < requiredFragments.length; i++ ) {
if ( fragmentName.startsWith(requiredFragments[i])) return false;
}
}
if ( optionalFragments != null ) {
for ( int i = 0; i < optionalFragments.length; i++ ) {
if ( fragmentName.startsWith(optionalFragments[i])) return false;
}
}
return ignoreUndefinedFragments;
}
protected class Template {
private boolean document;
private int length;
private String contentType;
private EnclosingTemplateFragment compiledTemplate = new EnclosingTemplateFragment();
private Map templateParameterDescriptors = new HashMap();
public Template(String template) {
length = template.length();
compileTemplate(template, 0, template.length(), compiledTemplate, false);
document = ContentType.determineIsDocument(template);
contentType = ContentType.determineContentType(template);
}
public int getLength() {
return length;
}
private int compileTemplate(String template, int currentPosition, int length, EnclosingTemplateFragment compiledTemplate, boolean condition) {
boolean tagsLeft = true;
do {
int nextOpeningVariable = template.indexOf(TAG_OPEN, currentPosition);
int nextOpeningCondition = template.indexOf(CONDITION_OPEN, currentPosition);
int nextClosingCondition = template.indexOf(CONDITION_CLOSE, currentPosition);
if ( nextClosingCondition != -1 && ( nextClosingCondition < nextOpeningCondition || nextOpeningCondition == -1 ) && ( nextClosingCondition < nextOpeningVariable || nextOpeningVariable == -1 ) ) {
compiledTemplate.addNestedTemplateFragment(new StaticTemplateFragment(template.substring(currentPosition, nextClosingCondition)));
currentPosition = nextClosingCondition+CONDITION_CLOSE.length();
return currentPosition;
} else if ( nextOpeningCondition != -1 && ( nextOpeningCondition < nextOpeningVariable || nextOpeningVariable == -1 ) ) {
compiledTemplate.addNestedTemplateFragment(new StaticTemplateFragment(template.substring(currentPosition, nextOpeningCondition)));
currentPosition = nextOpeningCondition+CONDITION_OPEN.length();
String variable = template.substring(currentPosition, template.indexOf(' ', currentPosition));
currentPosition += variable.length()+1;
ConditionalTemplateFragment conditionalTemplateFragment = new ConditionalTemplateFragment(variable);
compiledTemplate.addNestedTemplateFragment(conditionalTemplateFragment);
currentPosition = compileTemplate(template, currentPosition, length, conditionalTemplateFragment, true);
} else if ( nextOpeningVariable != -1 ) {
compiledTemplate.addNestedTemplateFragment(new StaticTemplateFragment(template.substring(currentPosition, nextOpeningVariable)));
currentPosition = nextOpeningVariable+TAG_OPEN.length();
int close = template.indexOf(TAG_CLOSE, currentPosition);
String variableName = template.substring(currentPosition, close);
String allowedContentTypes = null;
boolean required = !condition;
int modeSeparator = variableName.indexOf(SEPARATOR);
if ( modeSeparator > 0 ) {
String mode = variableName.substring(modeSeparator+1);
variableName = variableName.substring(0, modeSeparator);
int contentTypeSeparator = mode.indexOf(SEPARATOR);
if ( contentTypeSeparator > 0 ) {
allowedContentTypes = mode.substring(contentTypeSeparator+1);
mode = mode.substring(0, contentTypeSeparator);
}
if ( mode.equals(OPTIONAL) ) {
required = false;
} else if ( mode.equals(REQUIRED) ) {
required = true;
} else {
logger.log(Level.SEVERE, "Mode '"+mode+"' not allowed. Only optional or required are valid modes");
}
}
AnyValueDescriptor resourceValueDescriptor = new AnyValueDescriptor();
if ( !required || optionalParameters.contains(variableName)) {
// resourceValueDescriptor.addAllowedContentType(NullResource.CONTENT_TYPE);
}
if ( allowedContentTypes != null ) {
StringTokenizer tokenizer = new StringTokenizer(allowedContentTypes, ",");
while (tokenizer.hasMoreElements()) {
String contentTypeToken = tokenizer.nextToken();
resourceValueDescriptor.addAllowedContentType(contentTypeToken);
}
compiledTemplate.addNestedTemplateFragment(new Variable(variableName, resourceValueDescriptor.getAllowedContentTypes(), required));
} else {
// resourceValueDescriptor.addAllowedContentType(ContentType.ANY_TEXT);
compiledTemplate.addNestedTemplateFragment(new Variable(variableName, required));
}
ParameterDescriptor parameterDescriptor;
if ( required ) {
parameterDescriptor = new ParameterDescriptor(variableName, new ParameterMessage("templateVariable", new String[] { variableName }), resourceValueDescriptor);
} else {
parameterDescriptor = new ParameterDescriptor(variableName, new ParameterMessage("templateVariable", new String[] { variableName }), resourceValueDescriptor, new NullValue());
}
if ( !parameterDescriptions.contains(parameterDescriptor) ) {
parameterDescriptions.add(parameterDescriptor);
}
templateParameterDescriptors.put(variableName, parameterDescriptor);
currentPosition = close+TAG_CLOSE.length();
} else {
tagsLeft = false;
}
} while ( tagsLeft );
compiledTemplate.addNestedTemplateFragment(new StaticTemplateFragment(template.substring(currentPosition)));
return currentPosition;
}
public Map getTemplateParameterDescriptors() {
return templateParameterDescriptors;
}
public boolean isDocument() {
return document;
}
public String getContentType() {
return contentType;
}
public void evaluate(StringBuffer buffer, Map parameter) throws ProcessException {
evaluate(buffer, parameter, 0);
}
public void evaluate(StringBuffer buffer, Map parameter, int index) throws ProcessException {
compiledTemplate.render(buffer, parameter, index);
}
}
interface TemplateFragment {
public void render(StringBuffer buffer, Map parameter, int index) throws ProcessException;
}
class StaticTemplateFragment implements TemplateFragment {
private String content;
public StaticTemplateFragment(String content) {
this.content = content;
}
public void render(StringBuffer buffer, Map parameter, int index) throws ProcessException {
buffer.append(content);
}
}
class EnclosingTemplateFragment implements TemplateFragment {
private List nestedTemplateFragments = new ArrayList();
public void render(StringBuffer buffer, Map parameter, int index) throws ProcessException {
for ( Iterator i = nestedTemplateFragments.iterator(); i.hasNext(); ) {
TemplateFragment fragment = (TemplateFragment)i.next();
fragment.render(buffer, parameter, index);
}
}
public void addNestedTemplateFragment(Object fragment) {
nestedTemplateFragments.add(fragment);
}
}
class ConditionalTemplateFragment extends EnclosingTemplateFragment {
private String variable;
public ConditionalTemplateFragment(String variable) {
this.variable = variable;
optionalParameters.add(variable);
if ( parameterDescriptions.contains(variable) ) {
((AnyValueDescriptor)((ParameterDescriptor)parameterDescriptions.get(parameterDescriptions.indexOf(variable))).getValueDescriptor()).addAllowedContentType(NullValue.CONTENT_TYPE);
}
}
public String getVariable() {
return variable;
}
public void render(StringBuffer buffer, Map parameter, int index) throws ProcessException {
Object variableValue = parameter.get(variable);
if ( variableValue != null && !(variableValue instanceof NullValue) ) {
super.render(buffer, parameter, index);
}
}
}
class Variable implements TemplateFragment {
private String name;
private String[] allowedContentTypes;
private boolean required;
public Variable(String name, boolean required) {
this.name = name;
this.required = required;
}
public Variable(String name, String []allowedContentType, boolean required) {
this.name = name;
this.allowedContentTypes = allowedContentType;
this.required = required;
}
public void render(StringBuffer buffer, Map parameter, int index) throws ProcessException {
Object variableValue = parameter.get(name);
if ( variableValue == null ) variableValue = new NullValue();
if ( variableValue instanceof ArrayValue ) {
Value []array = (((ArrayValue)variableValue).getArray());
if ( index > array.length-1 ) {
variableValue = array[index % array.length];
} else {
variableValue = array[index];
}
}
if ( (required && variableValue instanceof NullValue )) {
throw new ProcessException(new ErrorMessage("templateRenderer/unsetValueNotAllowed", new Object[] { name }));
}
if ( allowedContentTypes != null && !ContentType.matches(allowedContentTypes, ((Value)variableValue).getContentType())) {
throw new ProcessException(new ErrorMessage("templateRenderer/contentTypeMismatch", new Object[] { name, ContentType.getContentTypesAsString(allowedContentTypes), ((Value)variableValue).getContentType() }));
}
if ( variableValue instanceof PrintableValue ) {
((PrintableValue)variableValue).print(buffer);
} else {
buffer.append(variableValue);
}
}
}
}