Java tutorial
/** * 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(); } }