/**
* Copyright 2011 meltmedia
*
* 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 org.xchain.framework.jsl;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.regex.Matcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
/**
* A utility class to build jsl template command classes.
*
* @author Christian Trimble
* @author Jason Rose
* @author Josh Kennedy
*/
public class TemplateSourceBuilder
{
public static Logger log = LoggerFactory.getLogger(TemplateSourceBuilder.class);
public static final String TEMPLATE_PACKAGE = "org.xchain.namespaces.jsl";
public static final String BASE_TEMPLATE_NAME = "TemplateCommand";
public static final String FIXED_PART_REGEX = "((?:[^{]+|\\{\\{)+)";
public static final String DYNAMIC_PART_REGEX = "(?:\\{((?:[^\\}\'\"]*|\"[^\"]*\"|\'[^\']*\')*)\\})";
public static final String ENCODING_REGEX = "(?:([\u0000-\u007f]+)|([^\u0000-\u007f]+))";
public static Pattern ATTRIBUTE_VALUE_TEMPLATE_PATTERN = null;
public static Pattern ENCODING_PATTERN = null;
static {
try {
ATTRIBUTE_VALUE_TEMPLATE_PATTERN = Pattern.compile("\\G"+FIXED_PART_REGEX+"|"+DYNAMIC_PART_REGEX+"|\\Z");
}
catch( PatternSyntaxException pse ) {
log.error("Could not compile attribute value template pattern.", pse);
}
try {
ENCODING_PATTERN = Pattern.compile(ENCODING_REGEX);
}
catch( PatternSyntaxException pse ) {
log.error("Could not compile encoding pattern.", pse);
}
}
/** A stack of the contexts for the source files we are working on. */
private LinkedList<Context> contextStack = new LinkedList<Context>();
private int commandId = 0;
public void pushContext( Context context )
{
contextStack.addFirst(context);
}
public Context popContext()
{
return contextStack.removeFirst();
}
public int nextCommandId()
{
return commandId++;
}
public void startSource(Map<String, String> transitionPrefixMapping, Set<String> transitionExcludeResultPrefixSet, boolean excludeResultPrefixBoundary)
{
Context context = new Context();
context.setTransitionPrefixMapping(transitionPrefixMapping);
context.setTransitionExcludeResultPrefixSet(transitionExcludeResultPrefixSet);
context.setExcludeResultPrefixBoundary(excludeResultPrefixBoundary);
pushContext(context);
// add the source result to the context.
context.setCommandIndex(nextCommandId());
startVirtualChain();
}
public SourceResult endSource()
{
// clean up all of the virtual chains.
Context context = contextStack.getFirst();
while( !context.getVirtualChainContextStack().isEmpty() ) {
endVirtualChain();
}
// pop the current context off of the stack.
context = popContext();
StringBuilder sourceBuilder = new StringBuilder();
sourceBuilder.append("package ").append(TEMPLATE_PACKAGE).append(";\n");
sourceBuilder.append("\n");
// add all of the imports.
sourceBuilder.append("import org.apache.commons.jxpath.JXPathContext;\n");
sourceBuilder.append("import org.xchain.framework.sax.CommandHandler;\n");
sourceBuilder.append("import org.xml.sax.Attributes;\n");
sourceBuilder.append("import org.xml.sax.ContentHandler;\n");
sourceBuilder.append("import org.xml.sax.SAXException;\n");
sourceBuilder.append("import org.xml.sax.helpers.AttributesImpl;\n");
sourceBuilder.append("import org.apache.commons.jxpath.JXPathContext;\n");
sourceBuilder.append("import javax.xml.namespace.QName;\n");
sourceBuilder.append("\n");
// add the class definition.
sourceBuilder.append("public class ").append(BASE_TEMPLATE_NAME).append(context.getCommandIndex()).append("\n");
sourceBuilder.append(" extends AbstractTemplateCommand\n");
sourceBuilder.append("{\n");
// add the execute template method. This is the hook into the abstract template command.
sourceBuilder.append(" public ").append(BASE_TEMPLATE_NAME).append(context.getCommandIndex()).append("()\n");
sourceBuilder.append(" {\n");
sourceBuilder.append(" super(").append(context.getElementCount()).append(");\n");
sourceBuilder.append(" }\n");
sourceBuilder.append(" public boolean executeTemplate(JXPathContext context)\n");
sourceBuilder.append(" throws Exception\n");
sourceBuilder.append(" {\n");
sourceBuilder.append(" boolean result = false;\n");
sourceBuilder.append(" Exception exception = null;\n");
sourceBuilder.append(" CommandHandler handler = getContentHandler();\n");
if( context.getExcludeResultPrefixBoundary() ) {
sourceBuilder.append(" handler.startExcludeResultPrefixContext();\n");
}
// we need to initialize the context of the output document with all of the namespace
// prefixes that occur above this commands first element.
for( Map.Entry<String, String> mapping : context.getTransitionPrefixMapping().entrySet() ) {
sourceBuilder.append(" handler.startPrefixMapping("+stringConstant(mapping.getKey())+", "+stringConstant(mapping.getValue())+");\n");
}
for( String excludeResultPrefix : context.getTransitionExcludeResultPrefixSet() ) {
sourceBuilder.append(" handler.startExcludeResultPrefix(").append(stringConstant(excludeResultPrefix)).append(");\n");
}
// code to call the first virtual chain.
sourceBuilder.append(" try {\n");
sourceBuilder.append(" result = virtualChain0(context);\n");
sourceBuilder.append(" }\n");
sourceBuilder.append(" catch( Exception e ) {\n");
sourceBuilder.append(" exception = e;\n");
sourceBuilder.append(" }\n");
// TODO: skip end prefix mappings if we are handling an error.
for( String excludeResultPrefix : context.getTransitionExcludeResultPrefixSet() ) {
sourceBuilder.append(" handler.endExcludeResultPrefix(").append(stringConstant(excludeResultPrefix)).append(");\n");
}
// close all of the namespace prefixes that were set above this element.
for( Map.Entry<String, String> mapping : context.getTransitionPrefixMapping().entrySet() ) {
sourceBuilder.append(" handler.endPrefixMapping("+stringConstant(mapping.getKey())+");\n");
}
if( context.getExcludeResultPrefixBoundary() ) {
sourceBuilder.append(" handler.endExcludeResultPrefixContext();\n");
}
sourceBuilder.append(" if( exception != null ) {\n");
sourceBuilder.append(" throw exception;\n");
sourceBuilder.append(" }\n");
// close the execute template method.
sourceBuilder.append(" return result;\n");
sourceBuilder.append(" }\n");
// if there has not been a virtual chain created, then we need to add one.
// append the source builder.
sourceBuilder.append(context.getMethodBuilder());
sourceBuilder.append("}\n");
SourceResult sourceResult = new SourceResult();
sourceResult.setSourceResourceName("org/xchain/namespaces/jsl/"+BASE_TEMPLATE_NAME+context.getCommandIndex()+".java");
sourceResult.setClassResourceName("org/xchain/namespaces/jsl/"+BASE_TEMPLATE_NAME+context.getCommandIndex()+".class");
sourceResult.setClassName("org.xchain.namespaces.jsl."+BASE_TEMPLATE_NAME+context.getCommandIndex());
sourceResult.setSource(sourceBuilder.toString());
return sourceResult;
}
/**
* Signals the start of a new virtual command. This method adds a call to the current virtual
* method context and then creates a new virtual method context.
*/
public void startVirtualChain()
{
// get the context.
Context context = contextStack.getFirst();
// get the virtual chain context index.
int index = context.nextVirtualChainIndex();
String virtualChainName = "virtualChain"+index;
// if there is currently a virtual chain on the stack, then it needs to call this virual chain.
if( !contextStack.getFirst().getVirtualChainContextStack().isEmpty() ) {
// change the mode to virtual chain.
changeBodyMode(BodyMode.VIRTUAL_CHAIN);
// get the current virtual chain builder.
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
// append the call to the new virtual chain.
indent(bodyBuilder).append(" result = ").append(virtualChainName).append("(context);\n");
}
// push the virtual chain context onto the stack.
contextStack.getFirst().getVirtualChainContextStack().addFirst(new VirtualChainContext(virtualChainName));
}
/**
* Signals the end of a virtual chain. The method removes the current virtual chain context from the stack and
* uses it to build the virtual method.
*/
public void endVirtualChain()
{
// change the mode to top level.
changeBodyMode(BodyMode.TOP_LEVEL);
// remove the top virtual chain builder from the stack.
VirtualChainContext virtualChainContext = contextStack.getFirst().getVirtualChainContextStack().removeFirst();
StringBuilder methodBuilder = contextStack.getFirst().getMethodBuilder();
methodBuilder.append("private boolean ").append(virtualChainContext.getName()).append("(JXPathContext context)\n");
methodBuilder.append(" throws Exception\n");
methodBuilder.append("{\n");
methodBuilder.append(" // the result and exception for this virtual chain\n");
methodBuilder.append(" boolean result = false;\n");
methodBuilder.append(" Exception exception = null;\n");
methodBuilder.append(" // the target for content handler events.\n");
methodBuilder.append(" CommandHandler handler = getContentHandler();\n");
methodBuilder.append(" // variables for building attribute objects.\n");
methodBuilder.append(" AttributesImpl attributes = new AttributesImpl();\n");
methodBuilder.append(" StringBuilder attributeValueBuilder = new StringBuilder();\n");
methodBuilder.append(" // variables for processing value-of elements.\n");
methodBuilder.append(" Object valueOfObject = null;\n");
methodBuilder.append(" String attributeValueString = null;\n");
// TODO: This should be a stack and the super classes stack should be removed.
methodBuilder.append(" // variables for parsing dynamic qNames.\n");
methodBuilder.append(" QName qName = null;\n");
methodBuilder.append(" // variables for processing comment elements.\n");
// add the indexes of the children chains.
methodBuilder.append(" int[] commandChildrenIndecies = {");
Iterator<Integer> childrenIndexIterator = virtualChainContext.getCommandIndexList().iterator();
while( childrenIndexIterator.hasNext() ) {
methodBuilder.append(childrenIndexIterator.next());
if( childrenIndexIterator.hasNext() ) {
methodBuilder.append(", ");
}
}
methodBuilder.append("};\n");
// add the indices of the children elements.
methodBuilder.append(" int[] elementChildrenIndecies = {");
Iterator<Integer> elementIndexIterator = virtualChainContext.getElementIndexList().iterator();
while( elementIndexIterator.hasNext() ) {
methodBuilder.append(elementIndexIterator.next());
if( elementIndexIterator.hasNext() ) {
methodBuilder.append(", ");
}
}
methodBuilder.append("};\n");
// add the body portion of the method, this contains all of the sax output code and command calls.
methodBuilder.append(virtualChainContext.getBodyBuilder());
// add the post process code.
methodBuilder.append(" return virtualPostProcess( context, exception, result, commandChildrenIndecies);\n");
// close the virtual chain.
methodBuilder.append("}\n");
}
public void appendCommandCall()
{
Context context = contextStack.getFirst();
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
// get the index for the next command child and increment the command count in the context.
Integer commandIndex = context.getCommandCount();
context.setCommandCount(commandIndex+1);
// if the virtual chain context has a depth greater than 1, then we need a virtual chain here.
if( virtualChainContext.getElementDepth() > 0 ) {
startVirtualChain();
virtualChainContext = context.getVirtualChainContextStack().getFirst();
}
// add the command index to the current virtual context.
virtualChainContext.getCommandIndexList().add(commandIndex);
// start the chain body mode if needed.
changeBodyMode(BodyMode.CHAIN);
virtualChainContext.getToExecuteIndexList().add(commandIndex);
}
public void startStartElement()
{
// push virtual chain is needed.
Context context = contextStack.getFirst();
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
// get the index for the next element child and increment the element count in the context.
Integer elementIndex = context.getElementCount();
context.setElementCount(elementIndex+1);
// track the current element index.
context.getElementIndexStack().addFirst(elementIndex);
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
// track the element indices that are in this virtual chain context.
virtualChainContext.getElementIndexList().add(elementIndex);
virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()+1);
}
public void endStartElement()
{
}
public void startEndElement()
{
// start the test for this element.
Context context = contextStack.getFirst();
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
// close virtual chain contexts, until we find one with an element depth of at least one.
while( virtualChainContext.getElementDepth() == 0 ) {
endVirtualChain();
virtualChainContext = context.getVirtualChainContextStack().getFirst();
}
changeBodyMode(BodyMode.END_ELEMENT_EVENTS);
// get the index for the next element child and increment the element count in the context.
Integer elementIndex = context.getElementIndexStack().getFirst();
// get the string builder.
StringBuilder bodyBuilder = virtualChainContext.getBodyBuilder();
// TODO: track that the end element has been output.
indent(bodyBuilder).append("if( isElementStarted(").append(elementIndex).append(") ) {\n");
}
public void endEndElement()
{
// end the test for this element.
Context context = contextStack.getFirst();
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
// get the index for the next element child and increment the element count in the context.
context.getElementIndexStack().removeFirst();
// get the string builder.
StringBuilder bodyBuilder = virtualChainContext.getBodyBuilder();
// TODO: track that the end element has been output.
indent(bodyBuilder).append("}\n");
virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()-1);
}
public void appendStartPrefixMapping( String prefix, String uri )
{
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
String escapedPrefix = stringConstant(prefix);
String escapedUri = stringConstant(uri);
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
indent(bodyBuilder).append("handler.startPrefixMapping(").append(escapedPrefix).append(", ").append(escapedUri).append(");\n");
// TODO: We need to track the new namespace.
indent(bodyBuilder).append("context.registerNamespace(").append(escapedPrefix).append(", ").append(escapedUri).append(");\n");
}
public void appendEndPrefixMapping( String prefix )
{
String escapedPrefix = stringConstant(prefix);
changeBodyMode(BodyMode.END_ELEMENT_EVENTS);
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
// TODO: We need to replace the namespace that was specified for this prefix.
indent(bodyBuilder).append("context.registerNamespace(").append(escapedPrefix).append(", ").append(escapedPrefix).append(");\n");
indent(bodyBuilder).append("handler.endPrefixMapping(").append(escapedPrefix).append(");\n");
}
public void appendStartExcludeResultPrefix( String prefix )
{
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
String escapedPrefix = stringConstant(prefix);
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
indent(bodyBuilder).append("handler.startExcludeResultPrefix(").append(escapedPrefix).append(");\n");
}
public void appendEndExcludeResultPrefix( String prefix )
{
changeBodyMode(BodyMode.END_ELEMENT_EVENTS);
String escapedPrefix = stringConstant(prefix);
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
indent(bodyBuilder).append("handler.endExcludeResultPrefix(").append(escapedPrefix).append(");\n");
}
public void appendContextStartPrefixMapping( String prefix, String uri )
{
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
String escapedPrefix = stringConstant(prefix);
String escapedUri = stringConstant(uri);
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
indent(bodyBuilder).append("context.registerNamespace(").append(escapedPrefix).append(", ").append(escapedUri).append(");\n");
}
public void appendContextEndPrefixMapping( String prefix )
{
String escapedPrefix = stringConstant(prefix);
changeBodyMode(BodyMode.END_ELEMENT_EVENTS);
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
indent(bodyBuilder).append("context.registerNamespace(").append(escapedPrefix).append(", ").append(escapedPrefix).append(");\n");
}
public void appendAttributeValueTemplate( String uri, String localName, String qName, String attributeValueTemplate )
throws SAXException
{
// escape the values that will be passed on.
String escapedUri = stringConstant(uri);
String escapedLocalName = stringConstant(localName);
String escapedQName = stringConstant(qName);
// change the mode of the virtual chain to BodyMode.START_ELEMENT_EVENTS if needed.
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
// get the string builder.
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
// parse the attribute value template and build code to create it.
Iterator<String> attributeValueIterator = parseAttributeValueTemplate(attributeValueTemplate).iterator();
while( attributeValueIterator.hasNext() ) {
// add code for the fixed part.
indent(bodyBuilder).append("attributeValueBuilder.append(").append(stringConstant(attributeValueIterator.next())).append(");\n");
// if there is a dynamic part, then add it.
if( attributeValueIterator.hasNext() ) {
indent(bodyBuilder).append("attributeValueString = (String)context.getValue(").append(stringConstant(attributeValueIterator.next())).append(", String.class);\n");
indent(bodyBuilder).append("attributeValueBuilder.append(attributeValueString != null ? attributeValueString : \"\");\n");
}
}
// add the code to add the attribute.
indent(bodyBuilder).append("attributes.addAttribute(").append(escapedUri).append(", ").append(escapedLocalName).append(", ").append(escapedQName).append(", \"CDATA\", attributeValueBuilder.toString());\n");
indent(bodyBuilder).append("attributeValueBuilder = new StringBuilder();\n");
}
/**
* Appends code to send a start element event to the handler.
*/
public void appendStartElement( String uri, String localName, String qName )
{
Context context = contextStack.getFirst();
Integer elementIndex = context.getElementIndexStack().getFirst();
// escape the values that will be passed on.
String escapedUri = stringConstant(uri);
String escapedLocalName = stringConstant(localName);
String escapedQName = stringConstant(qName);
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
// get the string builder.
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()+1);
StringBuilder bodyBuilder = virtualChainContext.getBodyBuilder();
// TODO: we need to track the element that the we output to the handler.
indent(bodyBuilder).append("trackStartElement(").append(elementIndex).append(");\n");
indent(bodyBuilder).append("handler.startElement(").append(escapedUri).append(", ").append(escapedLocalName).append(", ").append(escapedQName).append(", attributes);\n");
indent(bodyBuilder).append("attributes.clear();\n");
}
/**
* Appends code to send an end element event to the handler.
*/
public void appendEndElement( String uri, String localName, String qName )
{
Context context = contextStack.getFirst();
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()-1);
// get the index for the next element child and increment the element count in the context.
Integer elementIndex = context.getElementIndexStack().getFirst();
// if this element has any children that are filters, then they need to be backed out, before we can move on.
// TODO: execute the children that are filters.
// escape the values that will be passed on.
String escapedUri = stringConstant(uri);
String escapedLocalName = stringConstant(localName);
String escapedQName = stringConstant(qName);
changeBodyMode(BodyMode.END_ELEMENT_EVENTS);
// get the string builder.
StringBuilder bodyBuilder = context.getVirtualChainContextStack().getFirst().getBodyBuilder();
// TODO: track that the end element has been output.
indent(bodyBuilder).append("trackEndElement(").append(elementIndex).append(");\n");
indent(bodyBuilder).append("handler.endElement(").append(escapedUri).append(", ").append(escapedLocalName).append(", ").append(escapedQName).append(");\n");
}
/**
* Appends code to output a static string to the handler as a characters(char[], int, int) event to the handler.
*/
public void appendCharacters( String characters )
{
String escapedCharacters = stringConstant(characters);
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
indent(bodyBuilder).append("handler.characters(").append(escapedCharacters).append(".toCharArray(), 0, ").append(characters.length()).append(");\n");
}
public void appendIgnorableWhitespace( String ignorableWhitespace )
{
String escapedWhitespace = stringConstant(ignorableWhitespace);
// TODO: should we change the body mode to START_ELEMENT_EVENTS here?
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
indent(bodyBuilder).append("handler.ignorableWhitespace(").append(escapedWhitespace).append(".toCharArray(), 0, ").append(ignorableWhitespace.length()).append(");\n");
}
/**
* Appends code to output an xpath to the handler as a characters(char[], int, int) event to the handler.
* This should support disable-output-escaping="yes|no" and output the javax.xml.transform.disable-output-escaping processing instruction
* if needed.
*/
public void appendValueOf( String jxpath )
{
String escapedJXPath = stringConstant(jxpath);
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
StringBuilder bodyBuilder = contextStack.getFirst().getVirtualChainContextStack().getFirst().getBodyBuilder();
indent(bodyBuilder).append("valueOfObject = context.getValue(").append(escapedJXPath).append(");\n");
indent(bodyBuilder).append("if( valueOfObject != null ) {\n");
indent(bodyBuilder).append(" char[] valueOfChars = valueOfObject.toString().toCharArray();\n");
indent(bodyBuilder).append(" handler.characters(valueOfChars, 0, valueOfChars.length);\n");
indent(bodyBuilder).append("}\n");
}
public void appendStartComment()
{
Context context = contextStack.getFirst();
Integer elementIndex = context.getElementCount();
context.setElementCount(elementIndex+1);
context.getElementIndexStack().addFirst(elementIndex);
context.getVirtualChainContextStack().getFirst().getElementIndexList().add(elementIndex);
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
// get the string builder.
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()+1);
StringBuilder bodyBuilder = virtualChainContext.getBodyBuilder();
indent(bodyBuilder).append("trackStartElement(").append(elementIndex).append(");\n");
indent(bodyBuilder).append("handler.startComment();\n");
}
public void appendEndComment()
{
Context context = contextStack.getFirst();
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
// get the index for the next element child and increment the element count in the context.
Integer elementIndex = context.getElementIndexStack().getFirst();
changeBodyMode(BodyMode.END_ELEMENT_EVENTS);
// get the string builder.
StringBuilder bodyBuilder = virtualChainContext.getBodyBuilder();
// TODO: track that the end element has been output.
indent(bodyBuilder).append("if( isElementStarted(").append(elementIndex).append(") ) {\n");
indent(bodyBuilder).append(" trackEndElement(").append(elementIndex).append(");\n");
indent(bodyBuilder).append(" handler.endComment();\n");
indent(bodyBuilder).append("}\n");
// get the index for the next element child and increment the element count in the context.
context.getElementIndexStack().removeFirst();
virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()-1);
}
/**
* Appends code for the start of a <jsl:element/> tag.
*
* @param name the required name attribute.
* @param namespace the optional namespace attribute.
*/
public void appendStartDynamicElement( String name, String namespace )
{
Context context = contextStack.getFirst();
Integer elementIndex = context.getElementIndexStack().getFirst();
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
// get the string builder.
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
// this is counted in the start start element.
//virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()+1);
StringBuilder bodyBuilder = virtualChainContext.getBodyBuilder();
// TODO: we need to track the element that the we output to the handler.
indent(bodyBuilder).append("trackStartElement(").append(elementIndex).append(");\n");
// add the code to process the name and namespace attributes.
indent(bodyBuilder).append("qName = dynamicQName(context, ").append(stringConstant(name)).append(", ").append(stringConstant(namespace)).append(", true);\n");
indent(bodyBuilder).append("getDynamicElementStack().addFirst(qName);\n");
indent(bodyBuilder).append("handler.startElement(qName.getNamespaceURI(), qName.getLocalPart(), toPrefixedQName(qName), attributes);\n");
indent(bodyBuilder).append("attributes.clear();\n");
}
/**
* Appends code for the end of a <jsl:element/> tag.
*/
public void appendEndDynamicElement()
{
Context context = contextStack.getFirst();
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
// get the index for the next element child and increment the element count in the context.
Integer elementIndex = context.getElementIndexStack().getFirst();
changeBodyMode(BodyMode.END_ELEMENT_EVENTS);
// get the string builder.
StringBuilder bodyBuilder = virtualChainContext.getBodyBuilder();
// TODO: track that the end element has been output.
indent(bodyBuilder).append("trackEndElement(").append(elementIndex).append(");\n");
indent(bodyBuilder).append("qName = getDynamicElementStack().removeFirst();\n");
indent(bodyBuilder).append("handler.endElement(qName.getNamespaceURI(), qName.getLocalPart(), toPrefixedQName(qName));\n");
// this is counted in the endEndElement.
// virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()-1);
}
/**
* Appends code for the start of a <jsl:attribute/> tag.
*
* @param name the required name attribute.
* @param namespace the required namespace attribute.
*/
public void appendStartDynamicAttribute( String name, String namespace )
{
Context context = contextStack.getFirst();
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()+1);
changeBodyMode(BodyMode.START_ELEMENT_EVENTS);
// get the string builder.
StringBuilder bodyBuilder = context.getVirtualChainContextStack().getFirst().getBodyBuilder();
// add the code to process the name and namespace attributes.
indent(bodyBuilder).append("qName = dynamicQName(context, ").append(stringConstant(name)).append(", ").append(stringConstant(namespace)).append(", false);\n");
indent(bodyBuilder).append("getDynamicElementStack().addFirst(qName);\n");
indent(bodyBuilder).append("handler.startAttribute(qName.getNamespaceURI(), qName.getLocalPart(), toPrefixedQName(qName) );\n");
indent(bodyBuilder).append("attributes.clear();\n");
}
/**
* Appends code for the end of a <jsl:attribute/> tag.
*/
public void appendEndDynamicAttribute()
{
Context context = contextStack.getFirst();
VirtualChainContext virtualChainContext = context.getVirtualChainContextStack().getFirst();
changeBodyMode(BodyMode.END_ELEMENT_EVENTS);
// get the string builder.
StringBuilder bodyBuilder = context.getVirtualChainContextStack().getFirst().getBodyBuilder();
// TODO: track that the end element has been output.
indent(bodyBuilder).append("qName = getDynamicElementStack().removeFirst();\n");
indent(bodyBuilder).append("handler.endAttribute(qName.getNamespaceURI(), qName.getLocalPart(), toPrefixedQName(qName) );\n");
virtualChainContext.setElementDepth(virtualChainContext.getElementDepth()-1);
}
public void changeBodyMode( BodyMode newMode )
{
VirtualChainContext virtualChainContext = contextStack.getFirst().getVirtualChainContextStack().getFirst();
// if the body mode is changing, then we need to add code to the close the current mode
// and start the new mode.
if( newMode != virtualChainContext.getBodyMode() ) {
StringBuilder bodyBuilder = virtualChainContext.getBodyBuilder();
// stop the old mode.
switch( virtualChainContext.getBodyMode() ) {
case CHAIN:
indent(bodyBuilder).append(" result = executeChildren(context, new int[]{");
Iterator<Integer> toExecuteIndexIterator = virtualChainContext.getToExecuteIndexList().iterator();
while( toExecuteIndexIterator.hasNext() ) {
Integer toExecuteIndex = toExecuteIndexIterator.next();
bodyBuilder.append(toExecuteIndex);
if( toExecuteIndexIterator.hasNext() ) {
bodyBuilder.append(", ");
}
}
bodyBuilder.append("});\n");
virtualChainContext.getToExecuteIndexList().clear();
case VIRTUAL_CHAIN:
decrementIndent();
indent(bodyBuilder).append(" }\n");
indent(bodyBuilder).append(" catch( Exception e ) {\n");
indent(bodyBuilder).append(" exception = e;\n");
indent(bodyBuilder).append(" }\n");
indent(bodyBuilder).append("}\n");
break;
case START_ELEMENT_EVENTS:
decrementIndent();
indent(bodyBuilder).append(" }\n");
indent(bodyBuilder).append(" catch( SAXException e ) {\n");
indent(bodyBuilder).append(" registerSaxException(e);\n");
indent(bodyBuilder).append(" }\n");
indent(bodyBuilder).append(" catch( Exception e ) {\n");
indent(bodyBuilder).append(" registerSaxException(new SAXException(e));\n");
indent(bodyBuilder).append(" }\n");
indent(bodyBuilder).append(" finally {\n");
indent(bodyBuilder).append(" attributes.clear();\n");
indent(bodyBuilder).append(" attributeValueBuilder = new StringBuilder();\n");
indent(bodyBuilder).append(" }\n");
indent(bodyBuilder).append("}\n");
break;
case END_ELEMENT_EVENTS:
decrementIndent();
indent(bodyBuilder).append(" }\n");
indent(bodyBuilder).append(" catch( SAXException e ) {\n");
indent(bodyBuilder).append(" registerSaxException(e);\n");
indent(bodyBuilder).append(" }\n");
indent(bodyBuilder).append(" catch( Exception e ) {\n");
indent(bodyBuilder).append(" registerSaxException(new SAXException(e));\n");
indent(bodyBuilder).append(" }\n");
indent(bodyBuilder).append("}\n");
case TOP_LEVEL:
break;
default:
throw new IllegalStateException("Unknown virtual chain body mode encountered.");
}
// start the new mode.
switch( newMode ) {
case CHAIN:
case VIRTUAL_CHAIN:
case START_ELEMENT_EVENTS:
indent(bodyBuilder).append("if( exception == null && !result && !hasSaxExceptionFired() ) {\n");
indent(bodyBuilder).append(" try {\n");
incrementIndent();
break;
case END_ELEMENT_EVENTS:
indent(bodyBuilder).append("if( !hasSaxExceptionFired() ) {\n");
indent(bodyBuilder).append(" try {\n");
incrementIndent();
break;
case TOP_LEVEL:
break;
default:
throw new IllegalStateException("Unknown virtual chain body mode encountered.");
}
}
virtualChainContext.setBodyMode(newMode);
}
public StringBuilder indent(StringBuilder builder)
{
int indent = contextStack.getFirst().getIndent();
for( int i = 0; i < indent; i++ ) {
builder.append(" ");
}
return builder;
}
public void incrementIndent()
{
contextStack.getFirst().setIndent(contextStack.getFirst().getIndent()+1);
}
public void decrementIndent()
{
contextStack.getFirst().setIndent(contextStack.getFirst().getIndent()-1);
}
/**
* Parses an attribute value template into fixed and dynamic parts. This list will always start with a fixed part and
* then include alternating dynamic and fixed parts.
*/
public static List<String> parseAttributeValueTemplate( String attributeValueTemplate )
throws SAXException
{
// the result.
ArrayList<String> result = new ArrayList<String>();
// create the matcher.
Matcher matcher = ATTRIBUTE_VALUE_TEMPLATE_PATTERN.matcher(attributeValueTemplate);
while( matcher.find() ) {
String fixedPart = matcher.group(1);
String dynamicPart = matcher.group(2);
if( result.isEmpty() && fixedPart == null ) {
result.add("");
}
if( fixedPart != null ) {
result.add(fixedPart.replaceAll("\\{\\{", "{").replaceAll("\\}\\}", "}"));
}
if( dynamicPart != null ) {
result.add(dynamicPart);
}
}
if( !matcher.hitEnd() ) {
throw new SAXException("The attribute value template '"+attributeValueTemplate+"' has an error between characters "+matcher.regionStart()+" and "+matcher.regionEnd()+".");
}
return result;
}
/**
* The context for a sax template command.
*/
public static class Context
{
private LinkedList<VirtualChainContext> virtualChainContextStack = new LinkedList<VirtualChainContext>();
private StringBuilder methodBuilder = new StringBuilder();
private int commandCount = 0;
private int elementCount = 0;
private StringBuilder headerBuilder = new StringBuilder();
private StringBuilder footerBuilder = new StringBuilder();
private int indent = 0;
private int commandIndex = 0;
private int nextVirtualChainIndex = 0;
private Map<String, String> transitionPrefixMapping;
private Set<String> transitionExcludeResultPrefixSet;
private boolean excludeResultPrefixBoundary = false;
private LinkedList<Integer> elementIndexStack = new LinkedList<Integer>();
/** Returns the stack of virtual chain contexts. */
public LinkedList<VirtualChainContext> getVirtualChainContextStack() { return virtualChainContextStack; }
/** Returns the string builder used to store completed methods. */
public StringBuilder getMethodBuilder() { return methodBuilder; }
public void setCommandCount( int commandCount ) { this.commandCount = commandCount; }
public int getCommandCount() { return this.commandCount; }
public void setElementCount( int elementCount ) { this.elementCount = elementCount; }
public int getElementCount() { return this.elementCount; }
public StringBuilder getHeaderBuilder() { return this.headerBuilder; }
public StringBuilder getFooterBuilder() { return this.footerBuilder; }
public void setIndent( int indent ) { this.indent = indent; }
public int getIndent() { return this.indent; }
public void setCommandIndex( int commandIndex ) { this.commandIndex = commandIndex; }
public int getCommandIndex() { return this.commandIndex; }
public int nextVirtualChainIndex() {
return nextVirtualChainIndex++;
}
public Map<String, String> getTransitionPrefixMapping() { return transitionPrefixMapping; }
public void setTransitionPrefixMapping( Map<String, String> transitionPrefixMapping ) { this.transitionPrefixMapping = transitionPrefixMapping; }
public Set<String> getTransitionExcludeResultPrefixSet() { return transitionExcludeResultPrefixSet; }
public void setTransitionExcludeResultPrefixSet( Set<String> transitionExcludeResultPrefixSet ) { this.transitionExcludeResultPrefixSet = transitionExcludeResultPrefixSet; }
public boolean getExcludeResultPrefixBoundary() { return excludeResultPrefixBoundary; }
public void setExcludeResultPrefixBoundary( boolean excludeResultPrefixBoundary ) { this.excludeResultPrefixBoundary = excludeResultPrefixBoundary; }
public LinkedList<Integer> getElementIndexStack() { return elementIndexStack; }
}
public static enum BodyMode
{
/** The body mode when we are at the top level of a virtual method body. */
TOP_LEVEL,
/** The body mode when we are inside a set of chain calls. */
CHAIN,
/** The body mode when we are inside a set of handler calls that start elements or send character events. */
START_ELEMENT_EVENTS,
/** The body mode when we are inside a set of handler calls that end elements. */
END_ELEMENT_EVENTS,
VIRTUAL_CHAIN
}
/**
* The context for a virtual chain method.
*/
public static class VirtualChainContext
{
private String name = null;
private StringBuilder bodyBuilder = new StringBuilder();
private List<Integer> commandIndexList = new ArrayList<Integer>();
private List<Integer> elementIndexList = new ArrayList<Integer>();
private List<Integer> toExecuteIndexList = new ArrayList<Integer>();
private int elementDepth = 0;
private BodyMode bodyMode = BodyMode.TOP_LEVEL;
public VirtualChainContext( String name )
{
this.name = name;
}
/** Returns the name of this virtual chain method. */
public String getName() { return name; }
/** Returns the builer for the body of this method. */
public StringBuilder getBodyBuilder() { return bodyBuilder; }
/** Returns the list of child command indecies. */
public List<Integer> getCommandIndexList() { return commandIndexList; }
public List<Integer> getElementIndexList() { return elementIndexList; }
public List<Integer> getToExecuteIndexList() { return toExecuteIndexList; }
/** Returns the current body mode. */
public BodyMode getBodyMode() { return this.bodyMode; }
/** Sets the current body mode. */
public void setBodyMode( BodyMode bodyMode ) { this.bodyMode = bodyMode; };
public int getElementDepth() { return this.elementDepth; }
public void setElementDepth( int elementDepth ) { this.elementDepth = elementDepth; }
}
public static String stringConstant( String source )
{
// is the source is null, return null.
if( source == null ) {
return "((String)null)";
}
// the string buffer for the result.
StringBuilder builder = new StringBuilder();
// the initial double quote.
builder.append('\"');
// iterate over all of the characters, encoding all chars over 7 bits into utf escape sequences.
Matcher matcher = ENCODING_PATTERN.matcher(source);
while(matcher.find()) {
if( matcher.group(1) != null ) {
builder.append(matcher.group(1)
.replaceAll("\\\\", "\\\\\\\\")
.replaceAll("\\\"", "\\\\\"")
.replaceAll("\\\'", "\\\\\'")
.replaceAll("\r", "\\\\r")
.replaceAll("\t", "\\\\t")
.replaceAll("\b", "\\\\b")
.replaceAll("\n", "\\\\n")
.replaceAll("\f", "\\\\f"));
}
else {
// there has to be a better way to do this formatting...
String toUnicode = matcher.group(2);
for( int i = 0; i < toUnicode.length(); i++ ) {
builder.append("\\u");
String codePoint = Integer.toHexString(matcher.group(2).codePointAt(i));
for( int j = codePoint.length(); j < 4; j++ ) { builder.append("0"); }
builder.append(codePoint);
}
}
}
// terminating double quote.
builder.append('\"');
return builder.toString();
}
}