001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: KeyTable.java 468645 2006-10-28 06:57:24Z minchau $
020     */
021    package org.apache.xalan.transformer;
022    
023    import java.util.Hashtable;
024    import java.util.Vector;
025    
026    import javax.xml.transform.TransformerException;
027    
028    import org.apache.xalan.templates.KeyDeclaration;
029    import org.apache.xml.dtm.DTM;
030    import org.apache.xml.dtm.DTMIterator;
031    import org.apache.xml.utils.PrefixResolver;
032    import org.apache.xml.utils.QName;
033    import org.apache.xml.utils.WrappedRuntimeException;
034    import org.apache.xml.utils.XMLString;
035    import org.apache.xpath.XPathContext;
036    import org.apache.xpath.objects.XNodeSet;
037    import org.apache.xpath.objects.XObject;
038    
039    /**
040     * Table of element keys, keyed by document node.  An instance of this
041     * class is keyed by a Document node that should be matched with the
042     * root of the current context.
043     * @xsl.usage advanced
044     */
045    public class KeyTable
046    {
047      /**
048       * The document key.  This table should only be used with contexts
049       * whose Document roots match this key.
050       */
051      private int m_docKey;
052    
053      /**
054       * Vector of KeyDeclaration instances holding the key declarations.
055       */
056      private Vector m_keyDeclarations;
057    
058      /**
059       * Hold a cache of key() function result for each ref.
060       * Key is XMLString, the ref value
061       * Value is XNodeSet, the key() function result for the given ref value.
062       */
063      private Hashtable m_refsTable = null;
064    
065      /**
066       * Get the document root matching this key.  
067       *
068       * @return the document root matching this key
069       */
070      public int getDocKey()
071      {
072        return m_docKey;
073      }
074    
075      /** 
076       * The main iterator that will walk through the source  
077       * tree for this key.
078       */
079      private XNodeSet m_keyNodes;
080      
081      KeyIterator getKeyIterator()
082      {
083            return (KeyIterator)(m_keyNodes.getContainedIter());
084      }
085    
086      /**
087       * Build a keys table.
088       * @param doc The owner document key.
089       * @param nscontext The stylesheet's namespace context.
090       * @param name The key name
091       * @param keyDeclarations The stylesheet's xsl:key declarations.
092       *
093       * @throws javax.xml.transform.TransformerException
094       */
095      public KeyTable(
096              int doc, PrefixResolver nscontext, QName name, Vector keyDeclarations, XPathContext xctxt)
097                throws javax.xml.transform.TransformerException
098      {
099        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    }