1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: KeyTable.java 468645 2006-10-28 06:57:24Z minchau $
20 */
21package org.apache.xalan.transformer;
22
23import java.util.Hashtable;
24import java.util.Vector;
25
26import javax.xml.transform.TransformerException;
27
28import org.apache.xalan.templates.KeyDeclaration;
29import org.apache.xml.dtm.DTM;
30import org.apache.xml.dtm.DTMIterator;
31import org.apache.xml.utils.PrefixResolver;
32import org.apache.xml.utils.QName;
33import org.apache.xml.utils.WrappedRuntimeException;
34import org.apache.xml.utils.XMLString;
35import org.apache.xpath.XPathContext;
36import org.apache.xpath.objects.XNodeSet;
37import org.apache.xpath.objects.XObject;
38
39/**
40 * Table of element keys, keyed by document node.  An instance of this
41 * class is keyed by a Document node that should be matched with the
42 * root of the current context.
43 * @xsl.usage advanced
44 */
45public class KeyTable
46{
47  /**
48   * The document key.  This table should only be used with contexts
49   * whose Document roots match this key.
50   */
51  private int m_docKey;
52
53  /**
54   * Vector of KeyDeclaration instances holding the key declarations.
55   */
56  private Vector m_keyDeclarations;
57
58  /**
59   * Hold a cache of key() function result for each ref.
60   * Key is XMLString, the ref value
61   * Value is XNodeSet, the key() function result for the given ref value.
62   */
63  private Hashtable m_refsTable = null;
64
65  /**
66   * Get the document root matching this key.
67   *
68   * @return the document root matching this key
69   */
70  public int getDocKey()
71  {
72    return m_docKey;
73  }
74
75  /**
76   * The main iterator that will walk through the source
77   * tree for this key.
78   */
79  private XNodeSet m_keyNodes;
80
81  KeyIterator getKeyIterator()
82  {
83  	return (KeyIterator)(m_keyNodes.getContainedIter());
84  }
85
86  /**
87   * Build a keys table.
88   * @param doc The owner document key.
89   * @param nscontext The stylesheet's namespace context.
90   * @param name The key name
91   * @param keyDeclarations The stylesheet's xsl:key declarations.
92   *
93   * @throws javax.xml.transform.TransformerException
94   */
95  public KeyTable(
96          int doc, PrefixResolver nscontext, QName name, Vector keyDeclarations, XPathContext xctxt)
97            throws javax.xml.transform.TransformerException
98  {
99    m_docKey = doc;
100    m_keyDeclarations = keyDeclarations;
101    KeyIterator ki = new KeyIterator(name, keyDeclarations);
102
103    m_keyNodes = new XNodeSet(ki);
104    m_keyNodes.allowDetachToRelease(false);
105    m_keyNodes.setRoot(doc, xctxt);
106  }
107
108  /**
109   * Given a valid element key, return the corresponding node list.
110   *
111   * @param name The name of the key, which must match the 'name' attribute on xsl:key.
112   * @param ref The value that must match the value found by the 'match' attribute on xsl:key.
113   * @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.
114   */
115  public XNodeSet getNodeSetDTMByKey(QName name, XMLString ref)
116
117  {
118    XNodeSet refNodes = (XNodeSet) getRefsTable().get(ref);
119    // clone wiht reset the node set
120   try
121    {
122      if (refNodes != null)
123      {
124         refNodes = (XNodeSet) refNodes.cloneWithReset();
125       }
126    }
127    catch (CloneNotSupportedException e)
128    {
129      refNodes = null;
130    }
131
132    if (refNodes == null) {
133     //  create an empty XNodeSet
134      KeyIterator ki = (KeyIterator) (m_keyNodes).getContainedIter();
135      XPathContext xctxt = ki.getXPathContext();
136      refNodes = new XNodeSet(xctxt.getDTMManager()) {
137        public void setRoot(int nodeHandle, Object environment) {
138          // Root cannot be set on non-iterated node sets. Ignore it.
139        }
140      };
141      refNodes.reset();
142    }
143
144    return refNodes;
145  }
146
147  /**
148   * Get Key Name for this KeyTable
149   *
150   * @return Key name
151   */
152  public QName getKeyTableName()
153  {
154    return getKeyIterator().getName();
155  }
156
157  /**
158   * @return key declarations for the key associated to this KeyTable
159   */
160  private Vector getKeyDeclarations() {
161    int nDeclarations = m_keyDeclarations.size();
162    Vector keyDecls = new Vector(nDeclarations);
163
164    // Walk through each of the declarations made with xsl:key
165    for (int i = 0; i < nDeclarations; i++)
166    {
167      KeyDeclaration kd = (KeyDeclaration) m_keyDeclarations.elementAt(i);
168
169      // Add the declaration if the name on this key declaration
170      // matches the name on the iterator for this walker.
171      if (kd.getName().equals(getKeyTableName())) {
172        keyDecls.add(kd);
173      }
174    }
175
176    return keyDecls;
177  }
178
179  /**
180   * @return lazy initialized refs table associating evaluation of key function
181   *         with a XNodeSet
182   */
183  private Hashtable getRefsTable()
184  {
185    if (m_refsTable == null) {
186      // initial capacity set to a prime number to improve hash performance
187      m_refsTable = new Hashtable(89);
188
189      KeyIterator ki = (KeyIterator) (m_keyNodes).getContainedIter();
190      XPathContext xctxt = ki.getXPathContext();
191
192      Vector keyDecls = getKeyDeclarations();
193      int nKeyDecls = keyDecls.size();
194
195      int currentNode;
196      m_keyNodes.reset();
197      while (DTM.NULL != (currentNode = m_keyNodes.nextNode()))
198      {
199        try
200        {
201          for (int keyDeclIdx = 0; keyDeclIdx < nKeyDecls; keyDeclIdx++) {
202            KeyDeclaration keyDeclaration =
203                (KeyDeclaration) keyDecls.elementAt(keyDeclIdx);
204            XObject xuse =
205                keyDeclaration.getUse().execute(xctxt,
206                                                currentNode,
207                                                ki.getPrefixResolver());
208
209            if (xuse.getType() != xuse.CLASS_NODESET) {
210              XMLString exprResult = xuse.xstr();
211              addValueInRefsTable(xctxt, exprResult, currentNode);
212            } else {
213              DTMIterator i = ((XNodeSet)xuse).iterRaw();
214              int currentNodeInUseClause;
215
216              while (DTM.NULL != (currentNodeInUseClause = i.nextNode())) {
217                DTM dtm = xctxt.getDTM(currentNodeInUseClause);
218                XMLString exprResult =
219                    dtm.getStringValue(currentNodeInUseClause);
220                addValueInRefsTable(xctxt, exprResult, currentNode);
221              }
222            }
223          }
224        } catch (TransformerException te) {
225          throw new WrappedRuntimeException(te);
226        }
227      }
228    }
229    return m_refsTable;
230  }
231
232  /**
233   * Add an association between a ref and a node in the m_refsTable.
234   * Requires that m_refsTable != null
235   * @param xctxt XPath context
236   * @param ref the value of the use clause of the current key for the given node
237   * @param node the node to reference
238   */
239  private void addValueInRefsTable(XPathContext xctxt, XMLString ref, int node) {
240
241    XNodeSet nodes = (XNodeSet) m_refsTable.get(ref);
242    if (nodes == null)
243    {
244      nodes = new XNodeSet(node, xctxt.getDTMManager());
245      nodes.nextNode();
246      m_refsTable.put(ref, nodes);
247    }
248    else
249    {
250      // Nodes are passed to this method in document order.  Since we need to
251      // suppress duplicates, we only need to check against the last entry
252      // in each nodeset.  We use nodes.nextNode after each entry so we can
253      // easily compare node against the current node.
254      if (nodes.getCurrentNode() != node) {
255          nodes.mutableNodeset().addNode(node);
256          nodes.nextNode();
257      }
258    }
259  }
260}
261