001: /*
002: * Copyright 1999-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id: KeyTable.java,v 1.19 2005/01/23 00:37:17 mcnamara Exp $
018: */
019: package org.apache.xalan.transformer;
020:
021: import java.util.Hashtable;
022: import java.util.Vector;
023:
024: import javax.xml.transform.TransformerException;
025:
026: import org.apache.xalan.templates.KeyDeclaration;
027: import org.apache.xml.dtm.DTM;
028: import org.apache.xml.dtm.DTMIterator;
029: import org.apache.xml.utils.PrefixResolver;
030: import org.apache.xml.utils.QName;
031: import org.apache.xml.utils.WrappedRuntimeException;
032: import org.apache.xml.utils.XMLString;
033: import org.apache.xpath.XPathContext;
034: import org.apache.xpath.objects.XNodeSet;
035: import org.apache.xpath.objects.XObject;
036:
037: /**
038: * Table of element keys, keyed by document node. An instance of this
039: * class is keyed by a Document node that should be matched with the
040: * root of the current context.
041: * @xsl.usage advanced
042: */
043: public class KeyTable {
044: /**
045: * The document key. This table should only be used with contexts
046: * whose Document roots match this key.
047: */
048: private int m_docKey;
049:
050: /**
051: * Vector of KeyDeclaration instances holding the key declarations.
052: */
053: private Vector m_keyDeclarations;
054:
055: /**
056: * Hold a cache of key() function result for each ref.
057: * Key is XMLString, the ref value
058: * Value is XNodeSet, the key() function result for the given ref value.
059: */
060: private Hashtable m_refsTable = null;
061:
062: /**
063: * Get the document root matching this key.
064: *
065: * @return the document root matching this key
066: */
067: public int getDocKey() {
068: return m_docKey;
069: }
070:
071: /**
072: * The main iterator that will walk through the source
073: * tree for this key.
074: */
075: private XNodeSet m_keyNodes;
076:
077: KeyIterator getKeyIterator() {
078: return (KeyIterator) (m_keyNodes.getContainedIter());
079: }
080:
081: /**
082: * Build a keys table.
083: * @param doc The owner document key.
084: * @param nscontext The stylesheet's namespace context.
085: * @param name The key name
086: * @param keyDeclarations The stylesheet's xsl:key declarations.
087: *
088: * @throws javax.xml.transform.TransformerException
089: */
090: public KeyTable(int doc, PrefixResolver nscontext, QName name,
091: Vector keyDeclarations, XPathContext xctxt)
092: throws javax.xml.transform.TransformerException {
093: m_docKey = doc;
094: m_keyDeclarations = keyDeclarations;
095: KeyIterator ki = new KeyIterator(name, keyDeclarations);
096:
097: m_keyNodes = new XNodeSet(ki);
098: m_keyNodes.allowDetachToRelease(false);
099: m_keyNodes.setRoot(doc, xctxt);
100: }
101:
102: /**
103: * Given a valid element key, return the corresponding node list.
104: *
105: * @param name The name of the key, which must match the 'name' attribute on xsl:key.
106: * @param ref The value that must match the value found by the 'match' attribute on xsl:key.
107: * @return a set of nodes referenced by the key named <CODE>name</CODE> and the reference <CODE>ref</CODE>. If no node is referenced by this key, an empty node set is returned.
108: */
109: public XNodeSet getNodeSetDTMByKey(QName name, XMLString ref)
110:
111: {
112: XNodeSet refNodes = (XNodeSet) getRefsTable().get(ref);
113: // clone wiht reset the node set
114: try {
115: if (refNodes != null) {
116: refNodes = (XNodeSet) refNodes.cloneWithReset();
117: }
118: } catch (CloneNotSupportedException e) {
119: refNodes = null;
120: }
121:
122: if (refNodes == null) {
123: // create an empty XNodeSet
124: KeyIterator ki = (KeyIterator) (m_keyNodes)
125: .getContainedIter();
126: XPathContext xctxt = ki.getXPathContext();
127: refNodes = new XNodeSet(xctxt.getDTMManager()) {
128: public void setRoot(int nodeHandle, Object environment) {
129: // Root cannot be set on non-iterated node sets. Ignore it.
130: }
131: };
132: refNodes.reset();
133: }
134:
135: return refNodes;
136: }
137:
138: /**
139: * Get Key Name for this KeyTable
140: *
141: * @return Key name
142: */
143: public QName getKeyTableName() {
144: return getKeyIterator().getName();
145: }
146:
147: /**
148: * @return key declarations for the key associated to this KeyTable
149: */
150: private Vector getKeyDeclarations() {
151: int nDeclarations = m_keyDeclarations.size();
152: Vector keyDecls = new Vector(nDeclarations);
153:
154: // Walk through each of the declarations made with xsl:key
155: for (int i = 0; i < nDeclarations; i++) {
156: KeyDeclaration kd = (KeyDeclaration) m_keyDeclarations
157: .elementAt(i);
158:
159: // Add the declaration if the name on this key declaration
160: // matches the name on the iterator for this walker.
161: if (kd.getName().equals(getKeyTableName())) {
162: keyDecls.add(kd);
163: }
164: }
165:
166: return keyDecls;
167: }
168:
169: /**
170: * @return lazy initialized refs table associating evaluation of key function
171: * with a XNodeSet
172: */
173: private Hashtable getRefsTable() {
174: if (m_refsTable == null) {
175: // initial capacity set to a prime number to improve hash performance
176: m_refsTable = new Hashtable(89);
177:
178: KeyIterator ki = (KeyIterator) (m_keyNodes)
179: .getContainedIter();
180: XPathContext xctxt = ki.getXPathContext();
181:
182: Vector keyDecls = getKeyDeclarations();
183: int nKeyDecls = keyDecls.size();
184:
185: int currentNode;
186: m_keyNodes.reset();
187: while (DTM.NULL != (currentNode = m_keyNodes.nextNode())) {
188: try {
189: for (int keyDeclIdx = 0; keyDeclIdx < nKeyDecls; keyDeclIdx++) {
190: KeyDeclaration keyDeclaration = (KeyDeclaration) keyDecls
191: .elementAt(keyDeclIdx);
192: XObject xuse = keyDeclaration.getUse().execute(
193: xctxt, currentNode,
194: ki.getPrefixResolver());
195:
196: if (xuse.getType() != xuse.CLASS_NODESET) {
197: XMLString exprResult = xuse.xstr();
198: addValueInRefsTable(xctxt, exprResult,
199: currentNode);
200: } else {
201: DTMIterator i = ((XNodeSet) xuse).iterRaw();
202: int currentNodeInUseClause;
203:
204: while (DTM.NULL != (currentNodeInUseClause = i
205: .nextNode())) {
206: DTM dtm = xctxt
207: .getDTM(currentNodeInUseClause);
208: XMLString exprResult = dtm
209: .getStringValue(currentNodeInUseClause);
210: addValueInRefsTable(xctxt, exprResult,
211: currentNode);
212: }
213: }
214: }
215: } catch (TransformerException te) {
216: throw new WrappedRuntimeException(te);
217: }
218: }
219: }
220: return m_refsTable;
221: }
222:
223: /**
224: * Add an association between a ref and a node in the m_refsTable.
225: * Requires that m_refsTable != null
226: * @param xctxt XPath context
227: * @param ref the value of the use clause of the current key for the given node
228: * @param node the node to reference
229: */
230: private void addValueInRefsTable(XPathContext xctxt, XMLString ref,
231: int node) {
232:
233: XNodeSet nodes = (XNodeSet) m_refsTable.get(ref);
234: if (nodes == null) {
235: nodes = new XNodeSet(node, xctxt.getDTMManager());
236: nodes.nextNode();
237: m_refsTable.put(ref, nodes);
238: } else {
239: // Nodes are passed to this method in document order. Since we need to
240: // suppress duplicates, we only need to check against the last entry
241: // in each nodeset. We use nodes.nextNode after each entry so we can
242: // easily compare node against the current node.
243: if (nodes.getCurrentNode() != node) {
244: nodes.mutableNodeset().addNode(node);
245: nodes.nextNode();
246: }
247: }
248: }
249: }
|