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: AttributesImplSerializer.java 468654 2006-10-28 07:09:23Z minchau $
020     */
021    
022    package org.apache.xml.serializer;
023    
024    import java.util.Hashtable;
025    
026    import org.xml.sax.Attributes;
027    import org.xml.sax.helpers.AttributesImpl;
028    
029    /**
030     * This class extends org.xml.sax.helpers.AttributesImpl which implements org.
031     * xml.sax.Attributes. But for optimization this class adds a Hashtable for
032     * faster lookup of an index by qName, which is commonly done in the stream
033     * serializer.
034     * 
035     * @see org.xml.sax.Attributes
036     * 
037     * @xsl.usage internal
038     */
039    public final class AttributesImplSerializer extends AttributesImpl
040    {
041        /**
042         * Hash table of qName/index values to quickly lookup the index
043         * of an attributes qName.  qNames are in uppercase in the hash table
044         * to make the search case insensitive.
045         * 
046         * The keys to the hashtable to find the index are either
047         * "prefix:localName"  or "{uri}localName".
048         */
049        private final Hashtable m_indexFromQName = new Hashtable();
050        
051        private final StringBuffer m_buff = new StringBuffer();
052        
053        /**
054         * This is the number of attributes before switching to the hash table,
055         * and can be tuned, but 12 seems good for now - Brian M.
056         */
057        private static final int MAX = 12;
058        
059        /**
060         * One less than the number of attributes before switching to
061         * the Hashtable.
062         */
063        private static final int MAXMinus1 = MAX - 1;
064    
065        /**
066         * This method gets the index of an attribute given its qName.
067         * @param qname the qualified name of the attribute, e.g. "prefix1:locName1"
068         * @return the integer index of the attribute.
069         * @see org.xml.sax.Attributes#getIndex(String)
070         */
071        public final int getIndex(String qname)
072        {
073            int index;
074    
075            if (super.getLength() < MAX)
076            {
077                // if we haven't got too many attributes let the
078                // super class look it up
079                index = super.getIndex(qname);
080                return index;
081            }
082            // we have too many attributes and the super class is slow
083            // so find it quickly using our Hashtable.
084            Integer i = (Integer)m_indexFromQName.get(qname);
085            if (i == null)
086                index = -1;
087            else
088                index = i.intValue();
089            return index;
090        }
091        /**
092         * This method adds the attribute, but also records its qName/index pair in
093         * the hashtable for fast lookup by getIndex(qName).
094         * @param uri the URI of the attribute
095         * @param local the local name of the attribute
096         * @param qname the qualified name of the attribute
097         * @param type the type of the attribute
098         * @param val the value of the attribute
099         *
100         * @see org.xml.sax.helpers.AttributesImpl#addAttribute(String, String, String, String, String)
101         * @see #getIndex(String)
102         */
103        public final void addAttribute(
104            String uri,
105            String local,
106            String qname,
107            String type,
108            String val)
109        {
110            int index = super.getLength();
111            super.addAttribute(uri, local, qname, type, val);
112            // (index + 1) is now the number of attributes
113            // so either compare (index+1) to MAX, or compare index to (MAX-1)
114    
115            if (index < MAXMinus1)
116            {
117                return;
118            }
119            else if (index == MAXMinus1)
120            {
121                switchOverToHash(MAX);
122            }
123            else
124            {
125                /* add the key with the format of "prefix:localName" */
126                /* we have just added the attibute, its index is the old length */
127                Integer i = new Integer(index);
128                m_indexFromQName.put(qname, i);
129                
130                /* now add with key of the format "{uri}localName" */
131                m_buff.setLength(0);
132                m_buff.append('{').append(uri).append('}').append(local);
133                String key = m_buff.toString();
134                m_indexFromQName.put(key, i);
135            }
136            return;
137        }
138    
139        /**
140         * We are switching over to having a hash table for quick look
141         * up of attributes, but up until now we haven't kept any
142         * information in the Hashtable, so we now update the Hashtable.
143         * Future additional attributes will update the Hashtable as
144         * they are added.
145         * @param numAtts
146         */
147        private void switchOverToHash(int numAtts)
148        {
149            for (int index = 0; index < numAtts; index++)
150            {
151                String qName = super.getQName(index);
152                Integer i = new Integer(index);
153                m_indexFromQName.put(qName, i);
154                
155                // Add quick look-up to find with uri/local name pair
156                String uri = super.getURI(index);
157                String local = super.getLocalName(index);
158                m_buff.setLength(0);
159                m_buff.append('{').append(uri).append('}').append(local);
160                String key = m_buff.toString();
161                m_indexFromQName.put(key, i);
162            }
163        }
164    
165        /**
166         * This method clears the accumulated attributes.
167         *
168         * @see org.xml.sax.helpers.AttributesImpl#clear()
169         */
170        public final void clear()
171        {
172    
173            int len = super.getLength();
174            super.clear();
175            if (MAX <= len)
176            {
177                // if we have had enough attributes and are
178                // using the Hashtable, then clear the Hashtable too.
179                m_indexFromQName.clear();
180            }
181    
182        }
183    
184        /**
185         * This method sets the attributes, previous attributes are cleared,
186         * it also keeps the hashtable up to date for quick lookup via
187         * getIndex(qName).
188         * @param atts the attributes to copy into these attributes.
189         * @see org.xml.sax.helpers.AttributesImpl#setAttributes(Attributes)
190         * @see #getIndex(String)
191         */
192        public final void setAttributes(Attributes atts)
193        {
194    
195            super.setAttributes(atts);
196    
197            // we've let the super class add the attributes, but
198            // we need to keep the hash table up to date ourselves for the
199            // potentially new qName/index pairs for quick lookup. 
200            int numAtts = atts.getLength();
201            if (MAX <= numAtts)
202                switchOverToHash(numAtts);
203    
204        }
205        
206        /**
207         * This method gets the index of an attribute given its uri and locanName.
208         * @param uri the URI of the attribute name.
209         * @param localName the local namer (after the ':' ) of the attribute name.
210         * @return the integer index of the attribute.
211         * @see org.xml.sax.Attributes#getIndex(String)
212         */
213        public final int getIndex(String uri, String localName)
214        {
215            int index;
216    
217            if (super.getLength() < MAX)
218            {
219                // if we haven't got too many attributes let the
220                // super class look it up
221                index = super.getIndex(uri,localName);
222                return index;
223            }
224            // we have too many attributes and the super class is slow
225            // so find it quickly using our Hashtable.
226            // Form the key of format "{uri}localName"
227            m_buff.setLength(0);
228            m_buff.append('{').append(uri).append('}').append(localName);
229            String key = m_buff.toString();
230            Integer i = (Integer)m_indexFromQName.get(key);
231            if (i == null)
232                index = -1;
233            else
234                index = i.intValue();
235            return index;
236        }
237    }