blob: 9d38714d38bb75e4a6c16dc805279e546a87dc01 [file] [log] [blame]
/*
* @(#)$Id$
*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xalan" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 2001, Sun
* Microsystems., http://www.sun.com. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* @author Jacek Ambroziak
* @author Santiago Pericas-Geertsen
* @author Morten Jorgensen
* @author Erwin Bolwidt <ejb@klomp.org>
* @author G. Todd Miller
*/
package org.apache.xalan.xsltc.compiler;
import java.util.Iterator;
import java.util.HashSet;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import org.apache.bcel.util.*;
import org.apache.bcel.generic.*;
import org.apache.xalan.xsltc.DOM;
import org.apache.xalan.xsltc.dom.Axis;
import org.apache.xalan.xsltc.compiler.util.*;
import org.apache.xalan.xsltc.compiler.util.Type;
/**
* Mode gathers all the templates belonging to a given mode;
* it is responsible for generating an appropriate
* applyTemplates + (mode name) method in the translet.
*/
final class Mode implements Constants {
/**
* The name of this mode as defined in the stylesheet.
*/
private final QName _name;
/**
* A reference to the stylesheet object that owns this mode.
*/
private final Stylesheet _stylesheet;
/**
* The name of the method in which this mode is compiled.
*/
private final String _methodName;
/**
* A vector of all the templates in this mode.
*/
private Vector _templates;
/**
* Group for patterns with node()-type kernel and child axis.
*/
private Vector _childNodeGroup = null;
/**
* Test sequence for patterns with node()-type kernel and child axis.
*/
private TestSeq _childNodeTestSeq = null;
/**
* Group for patterns with node()-type kernel and attribute axis.
*/
private Vector _attribNodeGroup = null;
/**
* Test sequence for patterns with node()-type kernel and attribute axis.
*/
private TestSeq _attribNodeTestSeq = null;
/**
* Group for patterns with id() or key()-type kernel.
*/
private Vector _idxGroup = null;
/**
* Test sequence for patterns with id() or key()-type kernel.
*/
private TestSeq _idxTestSeq = null;
/**
* Group for patterns with any other kernel type.
*/
private Vector[] _patternGroups;
/**
* Test sequence for patterns with any other kernel type.
*/
private TestSeq[] _testSeq;
/**
* A mapping between patterns and instruction lists used by
* test sequences to avoid compiling the same pattern multiple
* times. Note that patterns whose kernels are "*", "node()"
* and "@*" can between shared by test sequences.
*/
private Hashtable _preCompiled = new Hashtable();
/**
* A mapping between templates and test sequences.
*/
private Hashtable _neededTemplates = new Hashtable();
/**
* A mapping between named templates and Mode objects.
*/
private Hashtable _namedTemplates = new Hashtable();
/**
* A mapping between templates and instruction handles.
*/
private Hashtable _templateIHs = new Hashtable();
/**
* A mapping between templates and instruction lists.
*/
private Hashtable _templateILs = new Hashtable();
/**
* A reference to the pattern matching the root node.
*/
private LocationPathPattern _rootPattern = null;
/**
* Stores ranges of template precendences for the compilation
* of apply-imports (a Hashtable for historical reasons).
*/
private Hashtable _importLevels = null;
/**
* A mapping between key names and keys.
*/
private Hashtable _keys = null;
/**
* Variable index for the current node used in code generation.
*/
private int _currentIndex;
/**
* Creates a new Mode.
*
* @param name A textual representation of the mode's QName
* @param stylesheet The Stylesheet in which the mode occured
* @param suffix A suffix to append to the method name for this mode
* (normally a sequence number - still in a String).
*/
public Mode(QName name, Stylesheet stylesheet, String suffix) {
_name = name;
_stylesheet = stylesheet;
_methodName = APPLY_TEMPLATES + suffix;
_templates = new Vector();
_patternGroups = new Vector[32];
}
/**
* Returns the name of the method (_not_ function) that will be
* compiled for this mode. Normally takes the form 'applyTemplates()'
* or * 'applyTemplates2()'.
*
* @return Method name for this mode
*/
public String functionName() {
return _methodName;
}
public String functionName(int min, int max) {
if (_importLevels == null) {
_importLevels = new Hashtable();
}
_importLevels.put(new Integer(max), new Integer(min));
return _methodName + '_' + max;
}
/**
* Add a pre-compiled pattern to this mode.
*/
public void addInstructionList(Pattern pattern,
InstructionList ilist)
{
_preCompiled.put(pattern, ilist);
}
/**
* Get the instruction list for a pre-compiled pattern. Used by
* test sequences to avoid compiling patterns more than once.
*/
public InstructionList getInstructionList(Pattern pattern) {
return (InstructionList) _preCompiled.get(pattern);
}
/**
* Shortcut to get the class compiled for this mode (will be inlined).
*/
private String getClassName() {
return _stylesheet.getClassName();
}
public Stylesheet getStylesheet() {
return _stylesheet;
}
public void addTemplate(Template template) {
_templates.addElement(template);
}
private Vector quicksort(Vector templates, int p, int r) {
if (p < r) {
final int q = partition(templates, p, r);
quicksort(templates, p, q);
quicksort(templates, q + 1, r);
}
return templates;
}
private int partition(Vector templates, int p, int r) {
final Template x = (Template)templates.elementAt(p);
int i = p - 1;
int j = r + 1;
while (true) {
while (x.compareTo((Template)templates.elementAt(--j)) > 0);
while (x.compareTo((Template)templates.elementAt(++i)) < 0);
if (i < j) {
templates.set(j, templates.set(i, templates.elementAt(j)));
}
else {
return j;
}
}
}
/**
* Process all the test patterns in this mode
*/
public void processPatterns(Hashtable keys) {
_keys = keys;
/*
System.out.println("Before Sort " + _name);
for (int i = 0; i < _templates.size(); i++) {
System.out.println("name = " + ((Template)_templates.elementAt(i)).getName());
System.out.println("pattern = " + ((Template)_templates.elementAt(i)).getPattern());
System.out.println("priority = " + ((Template)_templates.elementAt(i)).getPriority());
System.out.println("position = " + ((Template)_templates.elementAt(i)).getPosition());
}
*/
_templates = quicksort(_templates, 0, _templates.size() - 1);
/*
System.out.println("\n After Sort " + _name);
for (int i = 0; i < _templates.size(); i++) {
System.out.println("name = " + ((Template)_templates.elementAt(i)).getName());
System.out.println("pattern = " + ((Template)_templates.elementAt(i)).getPattern());
System.out.println("priority = " + ((Template)_templates.elementAt(i)).getPriority());
System.out.println("position = " + ((Template)_templates.elementAt(i)).getPosition());
}
*/
// Traverse all templates
final Enumeration templates = _templates.elements();
while (templates.hasMoreElements()) {
// Get the next template
final Template template = (Template)templates.nextElement();
/*
* Add this template to a table of named templates if it has a name.
* If there are multiple templates with the same name, all but one
* (the one with highest priority) will be disabled.
*/
if (template.isNamed() && !template.disabled()) {
_namedTemplates.put(template, this);
}
// Add this template to a test sequence if it has a pattern
final Pattern pattern = template.getPattern();
if (pattern != null) {
flattenAlternative(pattern, template, keys);
}
}
prepareTestSequences();
}
/**
* This method will break up alternative patterns (ie. unions of patterns,
* such as match="A/B | C/B") and add the basic patterns to their
* respective pattern groups.
*/
private void flattenAlternative(Pattern pattern,
Template template,
Hashtable keys) {
// Patterns on type id() and key() are special since they do not have
// any kernel node type (it can be anything as long as the node is in
// the id's or key's index).
if (pattern instanceof IdKeyPattern) {
final IdKeyPattern idkey = (IdKeyPattern)pattern;
idkey.setTemplate(template);
if (_idxGroup == null) _idxGroup = new Vector();
_idxGroup.add(pattern);
}
// Alternative patterns are broken up and re-processed recursively
else if (pattern instanceof AlternativePattern) {
final AlternativePattern alt = (AlternativePattern)pattern;
flattenAlternative(alt.getLeft(), template, keys);
flattenAlternative(alt.getRight(), template, keys);
}
// Finally we have a pattern that can be added to a test sequence!
else if (pattern instanceof LocationPathPattern) {
final LocationPathPattern lpp = (LocationPathPattern)pattern;
lpp.setTemplate(template);
addPatternToGroup(lpp);
}
}
/**
* Group patterns by NodeTests of their last Step
* Keep them sorted by priority within group
*/
private void addPatternToGroup(final LocationPathPattern lpp) {
// id() and key()-type patterns do not have a kernel type
if (lpp instanceof IdKeyPattern) {
addPattern(-1, lpp);
}
// Otherwise get the kernel pattern from the LPP
else {
// kernel pattern is the last (maybe only) Step
final StepPattern kernel = lpp.getKernelPattern();
if (kernel != null) {
addPattern(kernel.getNodeType(), lpp);
}
else if (_rootPattern == null ||
lpp.noSmallerThan(_rootPattern)) {
_rootPattern = lpp;
}
}
}
/**
* Adds a pattern to a pattern group
*/
private void addPattern(int kernelType, LocationPathPattern pattern) {
// Make sure the array of pattern groups is long enough
final int oldLength = _patternGroups.length;
if (kernelType >= oldLength) {
Vector[] newGroups = new Vector[kernelType * 2];
System.arraycopy(_patternGroups, 0, newGroups, 0, oldLength);
_patternGroups = newGroups;
}
// Find the vector to put this pattern into
Vector patterns;
if (kernelType == DOM.NO_TYPE) {
if (pattern.getAxis() == Axis.ATTRIBUTE) {
patterns = (_attribNodeGroup == null) ?
(_attribNodeGroup = new Vector(2)) : _attribNodeGroup;
}
else {
patterns = (_childNodeGroup == null) ?
(_childNodeGroup = new Vector(2)) : _childNodeGroup;
}
}
else {
patterns = (_patternGroups[kernelType] == null) ?
(_patternGroups[kernelType] = new Vector(2)) :
_patternGroups[kernelType];
}
if (patterns.size() == 0) {
patterns.addElement(pattern);
}
else {
boolean inserted = false;
for (int i = 0; i < patterns.size(); i++) {
final LocationPathPattern lppToCompare =
(LocationPathPattern)patterns.elementAt(i);
if (pattern.noSmallerThan(lppToCompare)) {
inserted = true;
patterns.insertElementAt(pattern, i);
break;
}
}
if (inserted == false) {
patterns.addElement(pattern);
}
}
}
/**
* Complete test sequences of a given type by adding all patterns
* from a given group.
*/
private void completeTestSequences(int nodeType, Vector patterns) {
if (patterns != null) {
if (_patternGroups[nodeType] == null) {
_patternGroups[nodeType] = patterns;
}
else {
final int m = patterns.size();
for (int j = 0; j < m; j++) {
addPattern(nodeType,
(LocationPathPattern) patterns.elementAt(j));
}
}
}
}
/**
* Build test sequences. The first step is to complete the test sequences
* by including patterns of "*" and "node()" kernel to all element test
* sequences, and of "@*" to all attribute test sequences.
*/
private void prepareTestSequences() {
final Vector starGroup = _patternGroups[DOM.ELEMENT];
final Vector atStarGroup = _patternGroups[DOM.ATTRIBUTE];
// Complete test sequence for "text()" with "child::node()"
completeTestSequences(DOM.TEXT, _childNodeGroup);
// Complete test sequence for "*" with "child::node()"
completeTestSequences(DOM.ELEMENT, _childNodeGroup);
// Complete test sequence for "pi()" with "child::node()"
completeTestSequences(DOM.PROCESSING_INSTRUCTION, _childNodeGroup);
// Complete test sequence for "comment()" with "child::node()"
completeTestSequences(DOM.COMMENT, _childNodeGroup);
// Complete test sequence for "@*" with "attribute::node()"
completeTestSequences(DOM.ATTRIBUTE, _attribNodeGroup);
final Vector names = _stylesheet.getXSLTC().getNamesIndex();
if (starGroup != null || atStarGroup != null ||
_childNodeGroup != null || _attribNodeGroup != null)
{
final int n = _patternGroups.length;
// Complete test sequence for user-defined types
for (int i = DOM.NTYPES; i < n; i++) {
if (_patternGroups[i] == null) continue;
final String name = (String) names.elementAt(i - DOM.NTYPES);
if (isAttributeName(name)) {
// If an attribute then copy "@*" to its test sequence
completeTestSequences(i, atStarGroup);
// And also copy "attribute::node()" to its test sequence
completeTestSequences(i, _attribNodeGroup);
}
else {
// If an element then copy "*" to its test sequence
completeTestSequences(i, starGroup);
// And also copy "child::node()" to its test sequence
completeTestSequences(i, _childNodeGroup);
}
}
}
_testSeq = new TestSeq[DOM.NTYPES + names.size()];
final int n = _patternGroups.length;
for (int i = 0; i < n; i++) {
final Vector patterns = _patternGroups[i];
if (patterns != null) {
final TestSeq testSeq = new TestSeq(patterns, i, this);
// System.out.println("testSeq[" + i + "] = " + testSeq);
testSeq.reduce();
_testSeq[i] = testSeq;
testSeq.findTemplates(_neededTemplates);
}
}
if (_childNodeGroup != null && _childNodeGroup.size() > 0) {
_childNodeTestSeq = new TestSeq(_childNodeGroup, -1, this);
_childNodeTestSeq.reduce();
_childNodeTestSeq.findTemplates(_neededTemplates);
}
/*
if (_attribNodeGroup != null && _attribNodeGroup.size() > 0) {
_attribNodeTestSeq = new TestSeq(_attribNodeGroup, -1, this);
_attribNodeTestSeq.reduce();
_attribNodeTestSeq.findTemplates(_neededTemplates);
}
*/
if (_idxGroup != null && _idxGroup.size() > 0) {
_idxTestSeq = new TestSeq(_idxGroup, this);
_idxTestSeq.reduce();
_idxTestSeq.findTemplates(_neededTemplates);
}
if (_rootPattern != null) {
// doesn't matter what is 'put', only key matters
_neededTemplates.put(_rootPattern.getTemplate(), this);
}
}
private void compileNamedTemplate(Template template,
ClassGenerator classGen) {
final ConstantPoolGen cpg = classGen.getConstantPool();
final InstructionList il = new InstructionList();
String methodName = Util.escape(template.getName().toString());
final NamedMethodGenerator methodGen =
new NamedMethodGenerator(ACC_PUBLIC,
org.apache.bcel.generic.Type.VOID,
new org.apache.bcel.generic.Type[] {
Util.getJCRefType(DOM_INTF_SIG),
Util.getJCRefType(NODE_ITERATOR_SIG),
Util.getJCRefType(TRANSLET_OUTPUT_SIG),
org.apache.bcel.generic.Type.INT
},
new String[] {
DOCUMENT_PNAME,
ITERATOR_PNAME,
TRANSLET_OUTPUT_PNAME,
NODE_PNAME
},
methodName,
getClassName(),
il, cpg);
il.append(template.compile(classGen, methodGen));
il.append(RETURN);
methodGen.stripAttributes(true);
methodGen.setMaxLocals();
methodGen.setMaxStack();
methodGen.removeNOPs();
classGen.addMethod(methodGen.getMethod());
}
private void compileTemplates(ClassGenerator classGen,
MethodGenerator methodGen,
InstructionHandle next)
{
Enumeration templates = _namedTemplates.keys();
while (templates.hasMoreElements()) {
final Template template = (Template)templates.nextElement();
compileNamedTemplate(template, classGen);
}
templates = _neededTemplates.keys();
while (templates.hasMoreElements()) {
final Template template = (Template)templates.nextElement();
if (template.hasContents()) {
// !!! TODO templates both named and matched
InstructionList til = template.compile(classGen, methodGen);
til.append(new GOTO_W(next));
_templateILs.put(template, til);
_templateIHs.put(template, til.getStart());
}
else {
// empty template
_templateIHs.put(template, next);
}
}
}
private void appendTemplateCode(InstructionList body) {
final Enumeration templates = _neededTemplates.keys();
while (templates.hasMoreElements()) {
final Object iList =
_templateILs.get(templates.nextElement());
if (iList != null) {
body.append((InstructionList)iList);
}
}
}
private void appendTestSequences(InstructionList body) {
final int n = _testSeq.length;
for (int i = 0; i < n; i++) {
final TestSeq testSeq = _testSeq[i];
if (testSeq != null) {
InstructionList il = testSeq.getInstructionList();
if (il != null)
body.append(il);
// else trivial TestSeq
}
}
}
public static void compileGetChildren(ClassGenerator classGen,
MethodGenerator methodGen,
int node) {
final ConstantPoolGen cpg = classGen.getConstantPool();
final InstructionList il = methodGen.getInstructionList();
final int git = cpg.addInterfaceMethodref(DOM_INTF,
GET_CHILDREN,
GET_CHILDREN_SIG);
il.append(methodGen.loadDOM());
il.append(new ILOAD(node));
il.append(new INVOKEINTERFACE(git, 2));
}
/**
* Compiles the default handling for DOM elements: traverse all children
*/
private InstructionList compileDefaultRecursion(ClassGenerator classGen,
MethodGenerator methodGen,
InstructionHandle next) {
final ConstantPoolGen cpg = classGen.getConstantPool();
final InstructionList il = new InstructionList();
final String applyTemplatesSig = classGen.getApplyTemplatesSig();
final int git = cpg.addInterfaceMethodref(DOM_INTF,
GET_CHILDREN,
GET_CHILDREN_SIG);
final int applyTemplates = cpg.addMethodref(getClassName(),
functionName(),
applyTemplatesSig);
il.append(classGen.loadTranslet());
il.append(methodGen.loadDOM());
il.append(methodGen.loadDOM());
il.append(new ILOAD(_currentIndex));
il.append(new INVOKEINTERFACE(git, 2));
il.append(methodGen.loadHandler());
il.append(new INVOKEVIRTUAL(applyTemplates));
il.append(new GOTO_W(next));
return il;
}
/**
* Compiles the default action for DOM text nodes and attribute nodes:
* output the node's text value
*/
private InstructionList compileDefaultText(ClassGenerator classGen,
MethodGenerator methodGen,
InstructionHandle next) {
final ConstantPoolGen cpg = classGen.getConstantPool();
final InstructionList il = new InstructionList();
final int chars = cpg.addInterfaceMethodref(DOM_INTF,
CHARACTERS,
CHARACTERS_SIG);
il.append(methodGen.loadDOM());
il.append(new ILOAD(_currentIndex));
il.append(methodGen.loadHandler());
il.append(new INVOKEINTERFACE(chars, 3));
il.append(new GOTO_W(next));
return il;
}
private InstructionList compileNamespaces(ClassGenerator classGen,
MethodGenerator methodGen,
boolean[] isNamespace,
boolean[] isAttribute,
boolean attrFlag,
InstructionHandle defaultTarget) {
final XSLTC xsltc = classGen.getParser().getXSLTC();
final ConstantPoolGen cpg = classGen.getConstantPool();
// Append switch() statement - namespace test dispatch loop
final Vector namespaces = xsltc.getNamespaceIndex();
final Vector names = xsltc.getNamesIndex();
final int namespaceCount = namespaces.size() + 1;
final int namesCount = names.size();
final InstructionList il = new InstructionList();
final int[] types = new int[namespaceCount];
final InstructionHandle[] targets = new InstructionHandle[types.length];
if (namespaceCount > 0) {
boolean compiled = false;
// Initialize targets for namespace() switch statement
for (int i = 0; i < namespaceCount; i++) {
targets[i] = defaultTarget;
types[i] = i;
}
// Add test sequences for known namespace types
for (int i = DOM.NTYPES; i < (DOM.NTYPES+namesCount); i++) {
if ((isNamespace[i]) && (isAttribute[i] == attrFlag)) {
String name = (String)names.elementAt(i-DOM.NTYPES);
String namespace = name.substring(0,name.lastIndexOf(':'));
final int type = xsltc.registerNamespace(namespace);
if ((i < _testSeq.length) &&
(_testSeq[i] != null)) {
targets[type] =
(_testSeq[i]).compile(classGen,
methodGen,
defaultTarget);
compiled = true;
}
}
}
// Return "null" if no test sequences were compiled
if (!compiled) return(null);
// Append first code in applyTemplates() - get type of current node
final int getNS = cpg.addInterfaceMethodref(DOM_INTF,
"getNamespaceType",
"(I)I");
il.append(methodGen.loadDOM());
il.append(new ILOAD(_currentIndex));
il.append(new INVOKEINTERFACE(getNS, 2));
il.append(new SWITCH(types, targets, defaultTarget));
return(il);
}
else {
return(null);
}
}
/**
* Compiles the applyTemplates() method and adds it to the translet.
* This is the main dispatch method.
*/
public void compileApplyTemplates(ClassGenerator classGen) {
final XSLTC xsltc = classGen.getParser().getXSLTC();
final ConstantPoolGen cpg = classGen.getConstantPool();
final Vector names = xsltc.getNamesIndex();
// Create the applyTemplates() method
final org.apache.bcel.generic.Type[] argTypes =
new org.apache.bcel.generic.Type[3];
argTypes[0] = Util.getJCRefType(DOM_INTF_SIG);
argTypes[1] = Util.getJCRefType(NODE_ITERATOR_SIG);
argTypes[2] = Util.getJCRefType(TRANSLET_OUTPUT_SIG);
final String[] argNames = new String[3];
argNames[0] = DOCUMENT_PNAME;
argNames[1] = ITERATOR_PNAME;
argNames[2] = TRANSLET_OUTPUT_PNAME;
final InstructionList mainIL = new InstructionList();
final MethodGenerator methodGen =
new MethodGenerator(ACC_PUBLIC | ACC_FINAL,
org.apache.bcel.generic.Type.VOID,
argTypes, argNames, functionName(),
getClassName(), mainIL,
classGen.getConstantPool());
methodGen.addException("org.apache.xalan.xsltc.TransletException");
// Create a local variable to hold the current node
final LocalVariableGen current;
current = methodGen.addLocalVariable2("current",
org.apache.bcel.generic.Type.INT,
mainIL.getEnd());
_currentIndex = current.getIndex();
// Create the "body" instruction list that will eventually hold the
// code for the entire method (other ILs will be appended).
final InstructionList body = new InstructionList();
body.append(NOP);
// Create an instruction list that contains the default next-node
// iteration
final InstructionList ilLoop = new InstructionList();
ilLoop.append(methodGen.loadIterator());
ilLoop.append(methodGen.nextNode());
ilLoop.append(DUP);
ilLoop.append(new ISTORE(_currentIndex));
// The body of this code can get very large - large than can be handled
// by a single IFNE(body.getStart()) instruction - need workaround:
final BranchHandle ifeq = ilLoop.append(new IFEQ(null));
final BranchHandle loop = ilLoop.append(new GOTO_W(null));
ifeq.setTarget(ilLoop.append(RETURN)); // applyTemplates() ends here!
final InstructionHandle ihLoop = ilLoop.getStart();
// Compile default handling of elements (traverse children)
InstructionList ilRecurse =
compileDefaultRecursion(classGen, methodGen, ihLoop);
InstructionHandle ihRecurse = ilRecurse.getStart();
// Compile default handling of text/attribute nodes (output text)
InstructionList ilText =
compileDefaultText(classGen, methodGen, ihLoop);
InstructionHandle ihText = ilText.getStart();
// Distinguish attribute/element/namespace tests for further processing
final int[] types = new int[DOM.NTYPES + names.size()];
for (int i = 0; i < types.length; i++) {
types[i] = i;
}
// Initialize isAttribute[] and isNamespace[] arrays
final boolean[] isAttribute = new boolean[types.length];
final boolean[] isNamespace = new boolean[types.length];
for (int i = 0; i < names.size(); i++) {
final String name = (String)names.elementAt(i);
isAttribute[i + DOM.NTYPES] = isAttributeName(name);
isNamespace[i + DOM.NTYPES] = isNamespaceName(name);
}
// Compile all templates - regardless of pattern type
compileTemplates(classGen, methodGen, ihLoop);
// Handle template with explicit "*" pattern
final TestSeq elemTest = _testSeq[DOM.ELEMENT];
InstructionHandle ihElem = ihRecurse;
if (elemTest != null)
ihElem = elemTest.compile(classGen, methodGen, ihRecurse);
// Handle template with explicit "@*" pattern
final TestSeq attrTest = _testSeq[DOM.ATTRIBUTE];
InstructionHandle ihAttr = ihText;
if (attrTest != null)
ihAttr = attrTest.compile(classGen, methodGen, ihAttr);
// Do tests for id() and key() patterns first
InstructionList ilKey = null;
if (_idxTestSeq != null) {
loop.setTarget(_idxTestSeq.compile(classGen, methodGen, body.getStart()));
ilKey = _idxTestSeq.getInstructionList();
}
else {
loop.setTarget(body.getStart());
}
// If there is a match on node() we need to replace ihElem
// and ihText if the priority of node() is higher
if (_childNodeTestSeq != null) {
// Compare priorities of node() and "*"
double nodePrio = _childNodeTestSeq.getPriority();
int nodePos = _childNodeTestSeq.getPosition();
double elemPrio = (0 - Double.MAX_VALUE);
int elemPos = Integer.MIN_VALUE;
if (elemTest != null) {
elemPrio = elemTest.getPriority();
elemPos = elemTest.getPosition();
}
if (elemPrio == Double.NaN || elemPrio < nodePrio ||
(elemPrio == nodePrio && elemPos < nodePos))
{
ihElem = _childNodeTestSeq.compile(classGen, methodGen, ihLoop);
}
// Compare priorities of node() and text()
final TestSeq textTest = _testSeq[DOM.TEXT];
double textPrio = (0 - Double.MAX_VALUE);
int textPos = Integer.MIN_VALUE;
if (textTest != null) {
textPrio = textTest.getPriority();
textPos = textTest.getPosition();
}
if (textPrio == Double.NaN || textPrio < nodePrio ||
(textPrio == nodePrio && textPos < nodePos))
{
ihText = _childNodeTestSeq.compile(classGen, methodGen, ihLoop);
_testSeq[DOM.TEXT] = _childNodeTestSeq;
}
}
// Handle templates with "ns:*" pattern
InstructionHandle elemNamespaceHandle = ihElem;
InstructionList nsElem = compileNamespaces(classGen, methodGen,
isNamespace, isAttribute,
false, ihElem);
if (nsElem != null) elemNamespaceHandle = nsElem.getStart();
// Handle templates with "ns:@*" pattern
InstructionHandle attrNamespaceHandle = ihAttr;
InstructionList nsAttr = compileNamespaces(classGen, methodGen,
isNamespace, isAttribute,
true, ihAttr);
if (nsAttr != null) attrNamespaceHandle = nsAttr.getStart();
// Handle templates with "ns:elem" or "ns:@attr" pattern
final InstructionHandle[] targets = new InstructionHandle[types.length];
for (int i = DOM.NTYPES; i < targets.length; i++) {
final TestSeq testSeq = _testSeq[i];
// Jump straight to namespace tests ?
if (isNamespace[i]) {
if (isAttribute[i])
targets[i] = attrNamespaceHandle;
else
targets[i] = elemNamespaceHandle;
}
// Test first, then jump to namespace tests
else if (testSeq != null) {
if (isAttribute[i])
targets[i] = testSeq.compile(classGen, methodGen,
attrNamespaceHandle);
else
targets[i] = testSeq.compile(classGen, methodGen,
elemNamespaceHandle);
}
else {
targets[i] = ihLoop;
}
}
// Handle pattern with match on root node - default: traverse children
targets[DOM.ROOT] = _rootPattern != null
? getTemplateInstructionHandle(_rootPattern.getTemplate())
: ihRecurse;
// Handle any pattern with match on text nodes - default: output text
targets[DOM.TEXT] = _testSeq[DOM.TEXT] != null
? _testSeq[DOM.TEXT].compile(classGen, methodGen, ihText)
: ihText;
// This DOM-type is not in use - default: process next node
targets[DOM.NAMESPACE] = ihLoop;
// Match unknown element in DOM - default: check for namespace match
targets[DOM.ELEMENT] = elemNamespaceHandle;
// Match unknown attribute in DOM - default: check for namespace match
targets[DOM.ATTRIBUTE] = attrNamespaceHandle;
// Match on processing instruction - default: process next node
InstructionHandle ihPI = ihLoop;
if (_childNodeTestSeq != null) ihPI = ihElem;
if (_testSeq[DOM.PROCESSING_INSTRUCTION] != null)
targets[DOM.PROCESSING_INSTRUCTION] =
_testSeq[DOM.PROCESSING_INSTRUCTION].
compile(classGen, methodGen, ihPI);
else
targets[DOM.PROCESSING_INSTRUCTION] = ihPI;
// Match on comments - default: process next node
InstructionHandle ihComment = ihLoop;
if (_childNodeTestSeq != null) ihComment = ihElem;
targets[DOM.COMMENT] = _testSeq[DOM.COMMENT] != null
? _testSeq[DOM.COMMENT].compile(classGen, methodGen, ihComment)
: ihComment;
// Now compile test sequences for various match patterns:
for (int i = DOM.NTYPES; i < targets.length; i++) {
final TestSeq testSeq = _testSeq[i];
// Jump straight to namespace tests ?
if ((testSeq == null) || (isNamespace[i])) {
if (isAttribute[i])
targets[i] = attrNamespaceHandle;
else
targets[i] = elemNamespaceHandle;
}
// Match on node type
else {
if (isAttribute[i])
targets[i] = testSeq.compile(classGen, methodGen,
attrNamespaceHandle);
else
targets[i] = testSeq.compile(classGen, methodGen,
elemNamespaceHandle);
}
}
if (ilKey != null) body.insert(ilKey);
// Append first code in applyTemplates() - get type of current node
final int getType = cpg.addInterfaceMethodref(DOM_INTF,
"getType", "(I)I");
body.append(methodGen.loadDOM());
body.append(new ILOAD(_currentIndex));
body.append(new INVOKEINTERFACE(getType, 2));
// Append switch() statement - main dispatch loop in applyTemplates()
InstructionHandle disp = body.append(new SWITCH(types, targets, ihLoop));
// Append all the "case:" statements
appendTestSequences(body);
// Append the actual template code
appendTemplateCode(body);
// Append NS:* node tests (if any)
if (nsElem != null) body.append(nsElem);
// Append NS:@* node tests (if any)
if (nsAttr != null) body.append(nsAttr);
// Append default action for element and root nodes
body.append(ilRecurse);
// Append default action for text and attribute nodes
body.append(ilText);
// putting together constituent instruction lists
mainIL.append(new GOTO_W(ihLoop));
mainIL.append(body);
// fall through to ilLoop
mainIL.append(ilLoop);
peepHoleOptimization(methodGen);
methodGen.stripAttributes(true);
methodGen.setMaxLocals();
methodGen.setMaxStack();
methodGen.removeNOPs();
classGen.addMethod(methodGen.getMethod());
// Compile method(s) for <xsl:apply-imports/> for this mode
if (_importLevels != null) {
Enumeration levels = _importLevels.keys();
while (levels.hasMoreElements()) {
Integer max = (Integer)levels.nextElement();
Integer min = (Integer)_importLevels.get(max);
compileApplyImports(classGen, min.intValue(), max.intValue());
}
}
}
private void compileTemplateCalls(ClassGenerator classGen,
MethodGenerator methodGen,
InstructionHandle next, int min, int max){
Enumeration templates = _neededTemplates.keys();
while (templates.hasMoreElements()) {
final Template template = (Template)templates.nextElement();
final int prec = template.getImportPrecedence();
if ((prec >= min) && (prec < max)) {
if (template.hasContents()) {
InstructionList til = template.compile(classGen, methodGen);
til.append(new GOTO_W(next));
_templateILs.put(template, til);
_templateIHs.put(template, til.getStart());
}
else {
// empty template
_templateIHs.put(template, next);
}
}
}
}
public void compileApplyImports(ClassGenerator classGen, int min, int max) {
final XSLTC xsltc = classGen.getParser().getXSLTC();
final ConstantPoolGen cpg = classGen.getConstantPool();
final Vector names = xsltc.getNamesIndex();
// Clear some datastructures
_namedTemplates = new Hashtable();
_neededTemplates = new Hashtable();
_templateIHs = new Hashtable();
_templateILs = new Hashtable();
_patternGroups = new Vector[32];
_rootPattern = null;
// IMPORTANT: Save orignal & complete set of templates!!!!
Vector oldTemplates = _templates;
// Gather templates that are within the scope of this import
_templates = new Vector();
final Enumeration templates = oldTemplates.elements();
while (templates.hasMoreElements()) {
final Template template = (Template)templates.nextElement();
final int prec = template.getImportPrecedence();
if ((prec >= min) && (prec < max)) addTemplate(template);
}
// Process all patterns from those templates
processPatterns(_keys);
// Create the applyTemplates() method
final org.apache.bcel.generic.Type[] argTypes =
new org.apache.bcel.generic.Type[3];
argTypes[0] = Util.getJCRefType(DOM_INTF_SIG);
argTypes[1] = Util.getJCRefType(NODE_ITERATOR_SIG);
argTypes[2] = Util.getJCRefType(TRANSLET_OUTPUT_SIG);
final String[] argNames = new String[3];
argNames[0] = DOCUMENT_PNAME;
argNames[1] = ITERATOR_PNAME;
argNames[2] = TRANSLET_OUTPUT_PNAME;
final InstructionList mainIL = new InstructionList();
final MethodGenerator methodGen =
new MethodGenerator(ACC_PUBLIC | ACC_FINAL,
org.apache.bcel.generic.Type.VOID,
argTypes, argNames, functionName()+'_'+max,
getClassName(), mainIL,
classGen.getConstantPool());
methodGen.addException("org.apache.xalan.xsltc.TransletException");
// Create the local variable to hold the current node
final LocalVariableGen current;
current = methodGen.addLocalVariable2("current",
org.apache.bcel.generic.Type.INT,
mainIL.getEnd());
_currentIndex = current.getIndex();
// Create the "body" instruction list that will eventually hold the
// code for the entire method (other ILs will be appended).
final InstructionList body = new InstructionList();
body.append(NOP);
// Create an instruction list that contains the default next-node
// iteration
final InstructionList ilLoop = new InstructionList();
ilLoop.append(methodGen.loadIterator());
ilLoop.append(methodGen.nextNode());
ilLoop.append(DUP);
ilLoop.append(new ISTORE(_currentIndex));
// The body of this code can get very large - large than can be handled
// by a single IFNE(body.getStart()) instruction - need workaround:
final BranchHandle ifeq = ilLoop.append(new IFEQ(null));
final BranchHandle loop = ilLoop.append(new GOTO_W(null));
ifeq.setTarget(ilLoop.append(RETURN)); // applyTemplates() ends here!
final InstructionHandle ihLoop = ilLoop.getStart();
// Compile default handling of elements (traverse children)
InstructionList ilRecurse =
compileDefaultRecursion(classGen, methodGen, ihLoop);
InstructionHandle ihRecurse = ilRecurse.getStart();
// Compile default handling of text/attribute nodes (output text)
InstructionList ilText =
compileDefaultText(classGen, methodGen, ihLoop);
InstructionHandle ihText = ilText.getStart();
// Distinguish attribute/element/namespace tests for further processing
final int[] types = new int[DOM.NTYPES + names.size()];
for (int i = 0; i < types.length; i++) {
types[i] = i;
}
final boolean[] isAttribute = new boolean[types.length];
final boolean[] isNamespace = new boolean[types.length];
for (int i = 0; i < names.size(); i++) {
final String name = (String)names.elementAt(i);
isAttribute[i+DOM.NTYPES] = isAttributeName(name);
isNamespace[i+DOM.NTYPES] = isNamespaceName(name);
}
// Compile all templates - regardless of pattern type
compileTemplateCalls(classGen, methodGen, ihLoop, min, max);
// Handle template with explicit "*" pattern
final TestSeq elemTest = _testSeq[DOM.ELEMENT];
InstructionHandle ihElem = ihRecurse;
if (elemTest != null) {
ihElem = elemTest.compile(classGen, methodGen, ihLoop);
}
// Handle template with explicit "@*" pattern
final TestSeq attrTest = _testSeq[DOM.ATTRIBUTE];
InstructionHandle ihAttr = ihLoop;
if (attrTest != null) {
ihAttr = attrTest.compile(classGen, methodGen, ihAttr);
}
// Do tests for id() and key() patterns first
InstructionList ilKey = null;
if (_idxTestSeq != null) {
loop.setTarget(_idxTestSeq.compile(classGen, methodGen, body.getStart()));
ilKey = _idxTestSeq.getInstructionList();
}
else {
loop.setTarget(body.getStart());
}
// If there is a match on node() we need to replace ihElem
// and ihText if the priority of node() is higher
if (_childNodeTestSeq != null) {
// Compare priorities of node() and "*"
double nodePrio = _childNodeTestSeq.getPriority();
int nodePos = _childNodeTestSeq.getPosition();
double elemPrio = (0 - Double.MAX_VALUE);
int elemPos = Integer.MIN_VALUE;
if (elemTest != null) {
elemPrio = elemTest.getPriority();
elemPos = elemTest.getPosition();
}
if (elemPrio == Double.NaN || elemPrio < nodePrio ||
(elemPrio == nodePrio && elemPos < nodePos))
{
ihElem = _childNodeTestSeq.compile(classGen, methodGen, ihLoop);
}
// Compare priorities of node() and text()
final TestSeq textTest = _testSeq[DOM.TEXT];
double textPrio = (0 - Double.MAX_VALUE);
int textPos = Integer.MIN_VALUE;
if (textTest != null) {
textPrio = textTest.getPriority();
textPos = textTest.getPosition();
}
if (textPrio == Double.NaN || textPrio < nodePrio ||
(textPrio == nodePrio && textPos < nodePos))
{
ihText = _childNodeTestSeq.compile(classGen, methodGen, ihLoop);
_testSeq[DOM.TEXT] = _childNodeTestSeq;
}
}
// Handle templates with "ns:*" pattern
InstructionHandle elemNamespaceHandle = ihElem;
InstructionList nsElem = compileNamespaces(classGen, methodGen,
isNamespace, isAttribute,
false, ihElem);
if (nsElem != null) elemNamespaceHandle = nsElem.getStart();
// Handle templates with "ns:@*" pattern
InstructionList nsAttr = compileNamespaces(classGen, methodGen,
isNamespace, isAttribute,
true, ihAttr);
InstructionHandle attrNamespaceHandle = ihAttr;
if (nsAttr != null) attrNamespaceHandle = nsAttr.getStart();
// Handle templates with "ns:elem" or "ns:@attr" pattern
final InstructionHandle[] targets = new InstructionHandle[types.length];
for (int i = DOM.NTYPES; i < targets.length; i++) {
final TestSeq testSeq = _testSeq[i];
// Jump straight to namespace tests ?
if (isNamespace[i]) {
if (isAttribute[i])
targets[i] = attrNamespaceHandle;
else
targets[i] = elemNamespaceHandle;
}
// Test first, then jump to namespace tests
else if (testSeq != null) {
if (isAttribute[i])
targets[i] = testSeq.compile(classGen, methodGen,
attrNamespaceHandle);
else
targets[i] = testSeq.compile(classGen, methodGen,
elemNamespaceHandle);
}
else {
targets[i] = ihLoop;
}
}
// Handle pattern with match on root node - default: traverse children
targets[DOM.ROOT] = _rootPattern != null
? getTemplateInstructionHandle(_rootPattern.getTemplate())
: ihRecurse;
// Handle any pattern with match on text nodes - default: loop
targets[DOM.TEXT] = _testSeq[DOM.TEXT] != null
? _testSeq[DOM.TEXT].compile(classGen, methodGen, ihText)
: ihText;
// This DOM-type is not in use - default: process next node
targets[DOM.NAMESPACE] = ihLoop;
// Match unknown element in DOM - default: check for namespace match
targets[DOM.ELEMENT] = elemNamespaceHandle;
// Match unknown attribute in DOM - default: check for namespace match
targets[DOM.ATTRIBUTE] = attrNamespaceHandle;
// Match on processing instruction - default: loop
InstructionHandle ihPI = ihLoop;
if (_childNodeTestSeq != null) ihPI = ihElem;
if (_testSeq[DOM.PROCESSING_INSTRUCTION] != null) {
targets[DOM.PROCESSING_INSTRUCTION] =
_testSeq[DOM.PROCESSING_INSTRUCTION].
compile(classGen, methodGen, ihPI);
}
else {
targets[DOM.PROCESSING_INSTRUCTION] = ihPI;
}
// Match on comments - default: process next node
InstructionHandle ihComment = ihLoop;
if (_childNodeTestSeq != null) ihComment = ihElem;
targets[DOM.COMMENT] = _testSeq[DOM.COMMENT] != null
? _testSeq[DOM.COMMENT].compile(classGen, methodGen, ihComment)
: ihComment;
// Now compile test sequences for various match patterns:
for (int i = DOM.NTYPES; i < targets.length; i++) {
final TestSeq testSeq = _testSeq[i];
// Jump straight to namespace tests ?
if ((testSeq == null) || (isNamespace[i])) {
if (isAttribute[i])
targets[i] = attrNamespaceHandle;
else
targets[i] = elemNamespaceHandle;
}
// Match on node type
else {
if (isAttribute[i])
targets[i] = testSeq.compile(classGen, methodGen,
attrNamespaceHandle);
else
targets[i] = testSeq.compile(classGen, methodGen,
elemNamespaceHandle);
}
}
if (ilKey != null) body.insert(ilKey);
// Append first code in applyTemplates() - get type of current node
final int getType = cpg.addInterfaceMethodref(DOM_INTF,
"getType", "(I)I");
body.append(methodGen.loadDOM());
body.append(new ILOAD(_currentIndex));
body.append(new INVOKEINTERFACE(getType, 2));
// Append switch() statement - main dispatch loop in applyTemplates()
InstructionHandle disp = body.append(new SWITCH(types,targets,ihLoop));
// Append all the "case:" statements
appendTestSequences(body);
// Append the actual template code
appendTemplateCode(body);
// Append NS:* node tests (if any)
if (nsElem != null) body.append(nsElem);
// Append NS:@* node tests (if any)
if (nsAttr != null) body.append(nsAttr);
// Append default action for element and root nodes
body.append(ilRecurse);
// Append default action for text and attribute nodes
body.append(ilText);
// putting together constituent instruction lists
mainIL.append(new GOTO_W(ihLoop));
mainIL.append(body);
// fall through to ilLoop
mainIL.append(ilLoop);
peepHoleOptimization(methodGen);
methodGen.stripAttributes(true);
methodGen.setMaxLocals();
methodGen.setMaxStack();
methodGen.removeNOPs();
classGen.addMethod(methodGen.getMethod());
// Restore original (complete) set of templates for this transformation
_templates = oldTemplates;
}
/**
* Peephole optimization: Remove sequences of [ALOAD, POP].
*/
private void peepHoleOptimization(MethodGenerator methodGen) {
InstructionList il = methodGen.getInstructionList();
InstructionFinder find = new InstructionFinder(il);
InstructionHandle ih;
String pattern;
// Remove seqences of ALOAD, POP (GTM)
pattern = "`ALOAD'`POP'`Instruction'";
for(Iterator iter=find.search(pattern); iter.hasNext();){
InstructionHandle[] match = (InstructionHandle[])iter.next();
try {
if ((!match[0].hasTargeters()) && (!match[1].hasTargeters())) {
il.delete(match[0], match[1]);
}
}
catch (TargetLostException e) {
// TODO: move target down into the list
}
}
// Replace sequences of ILOAD_?, ALOAD_?, SWAP with ALOAD_?, ILOAD_?
pattern = "`ILOAD'`ALOAD'`SWAP'`Instruction'";
for(Iterator iter=find.search(pattern); iter.hasNext();){
InstructionHandle[] match = (InstructionHandle[])iter.next();
try {
org.apache.bcel.generic.Instruction iload;
org.apache.bcel.generic.Instruction aload;
if ((!match[0].hasTargeters()) &&
(!match[1].hasTargeters()) &&
(!match[2].hasTargeters())) {
iload = match[0].getInstruction();
aload = match[1].getInstruction();
il.insert(match[0], aload);
il.insert(match[0], iload);
il.delete(match[0], match[2]);
}
}
catch (TargetLostException e) {
// TODO: move target down into the list
}
}
// Replace sequences of ALOAD_1, ALOAD_1 with ALOAD_1, DUP
pattern = "`ALOAD_1'`ALOAD_1'`Instruction'";
for(Iterator iter=find.search(pattern); iter.hasNext();){
InstructionHandle[] match = (InstructionHandle[])iter.next();
try {
org.apache.bcel.generic.Instruction iload;
org.apache.bcel.generic.Instruction aload;
if ((!match[0].hasTargeters()) && (!match[1].hasTargeters())) {
il.insert(match[1], new DUP());
il.delete(match[1]);
}
}
catch (TargetLostException e) {
// TODO: move target down into the list
}
}
}
public InstructionHandle getTemplateInstructionHandle(Template template) {
return (InstructionHandle)_templateIHs.get(template);
}
/**
* Auxiliary method to determine if a qname is an attribute.
*/
private static boolean isAttributeName(String qname) {
final int col = qname.lastIndexOf(':') + 1;
return (qname.charAt(col) == '@');
}
/**
* Auxiliary method to determine if a qname is a namespace
* qualified "*".
*/
private static boolean isNamespaceName(String qname) {
final int col = qname.lastIndexOf(':');
return (col > -1 && qname.charAt(qname.length()-1) == '*');
}
}