1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 */
4/*
5 * Licensed to the Apache Software Foundation (ASF) under one or more
6 * contributor license agreements.  See the NOTICE file distributed with
7 * this work for additional information regarding copyright ownership.
8 * The ASF licenses this file to You under the Apache License, Version 2.0
9 * (the "License"); you may not use this file except in compliance with
10 * the License.  You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20/*
21 * $Id: AttributesImplSerializer.java,v 1.2.4.1 2005/09/15 08:15:14 suresh_emailid Exp $
22 */
23
24package com.sun.org.apache.xml.internal.serializer;
25
26import java.util.HashMap;
27import java.util.Map;
28import org.xml.sax.Attributes;
29import org.xml.sax.helpers.AttributesImpl;
30
31/**
32 * This class extends org.xml.sax.helpers.AttributesImpl which implements org.
33 * xml.sax.Attributes. But for optimization this class adds a Map for
34 * faster lookup of an index by qName, which is commonly done in the stream
35 * serializer.
36 *
37 * @see org.xml.sax.Attributes
38 *
39 * @xsl.usage internal
40 */
41public final class AttributesImplSerializer extends AttributesImpl
42{
43    /**
44     * Hash table of qName/index values to quickly lookup the index
45     * of an attributes qName.  qNames are in uppercase in the hash table
46     * to make the search case insensitive.
47     *
48     * The keys to the hashtable to find the index are either
49     * "prefix:localName"  or "{uri}localName".
50     */
51    private final Map<String, Integer> m_indexFromQName = new HashMap<>();
52
53    private final StringBuffer m_buff = new StringBuffer();
54
55    /**
56     * This is the number of attributes before switching to the hash table,
57     * and can be tuned, but 12 seems good for now - Brian M.
58     */
59    private static final int MAX = 12;
60
61    /**
62     * One less than the number of attributes before switching to
63     * the Map.
64     */
65    private static final int MAXMinus1 = MAX - 1;
66
67    /**
68     * This method gets the index of an attribute given its qName.
69     * @param qname the qualified name of the attribute, e.g. "prefix1:locName1"
70     * @return the integer index of the attribute.
71     * @see org.xml.sax.Attributes#getIndex(String)
72     */
73    public final int getIndex(String qname)
74    {
75        int index;
76
77        if (super.getLength() < MAX)
78        {
79            // if we haven't got too many attributes let the
80            // super class look it up
81            index = super.getIndex(qname);
82            return index;
83        }
84        // we have too many attributes and the super class is slow
85        // so find it quickly using our Map.
86        Integer i = m_indexFromQName.get(qname);
87        if (i == null)
88            index = -1;
89        else
90            index = i.intValue();
91        return index;
92    }
93    /**
94     * This method adds the attribute, but also records its qName/index pair in
95     * the hashtable for fast lookup by getIndex(qName).
96     * @param uri the URI of the attribute
97     * @param local the local name of the attribute
98     * @param qname the qualified name of the attribute
99     * @param type the type of the attribute
100     * @param val the value of the attribute
101     *
102     * @see org.xml.sax.helpers.AttributesImpl#addAttribute(String, String, String, String, String)
103     * @see #getIndex(String)
104     */
105    public final void addAttribute(
106        String uri,
107        String local,
108        String qname,
109        String type,
110        String val)
111    {
112        int index = super.getLength();
113        super.addAttribute(uri, local, qname, type, val);
114        // (index + 1) is now the number of attributes
115        // so either compare (index+1) to MAX, or compare index to (MAX-1)
116
117        if (index < MAXMinus1)
118        {
119            return;
120        }
121        else if (index == MAXMinus1)
122        {
123            switchOverToHash(MAX);
124        }
125        else
126        {
127            /* add the key with the format of "prefix:localName" */
128            /* we have just added the attibute, its index is the old length */
129            Integer i = index;
130            m_indexFromQName.put(qname, i);
131
132            /* now add with key of the format "{uri}localName" */
133            m_buff.setLength(0);
134            m_buff.append('{').append(uri).append('}').append(local);
135            String key = m_buff.toString();
136            m_indexFromQName.put(key, i);
137        }
138    }
139
140    /**
141     * We are switching over to having a hash table for quick look
142     * up of attributes, but up until now we haven't kept any
143     * information in the Map, so we now update the Map.
144     * Future additional attributes will update the Map as
145     * they are added.
146     * @param numAtts
147     */
148    private void switchOverToHash(int numAtts)
149    {
150        for (int index = 0; index < numAtts; index++)
151        {
152            String qName = super.getQName(index);
153            Integer i = index;
154            m_indexFromQName.put(qName, i);
155
156            // Add quick look-up to find with uri/local name pair
157            String uri = super.getURI(index);
158            String local = super.getLocalName(index);
159            m_buff.setLength(0);
160            m_buff.append('{').append(uri).append('}').append(local);
161            String key = m_buff.toString();
162            m_indexFromQName.put(key, i);
163        }
164    }
165
166    /**
167     * This method clears the accumulated attributes.
168     *
169     * @see org.xml.sax.helpers.AttributesImpl#clear()
170     */
171    public final void clear()
172    {
173
174        int len = super.getLength();
175        super.clear();
176        if (MAX <= len)
177        {
178            // if we have had enough attributes and are
179            // using the Map, then clear the Map too.
180            m_indexFromQName.clear();
181        }
182
183    }
184
185    /**
186     * This method sets the attributes, previous attributes are cleared,
187     * it also keeps the hashtable up to date for quick lookup via
188     * getIndex(qName).
189     * @param atts the attributes to copy into these attributes.
190     * @see org.xml.sax.helpers.AttributesImpl#setAttributes(Attributes)
191     * @see #getIndex(String)
192     */
193    public final void setAttributes(Attributes atts)
194    {
195
196        super.setAttributes(atts);
197
198        // we've let the super class add the attributes, but
199        // we need to keep the hash table up to date ourselves for the
200        // potentially new qName/index pairs for quick lookup.
201        int numAtts = atts.getLength();
202        if (MAX <= numAtts)
203            switchOverToHash(numAtts);
204
205    }
206
207    /**
208     * This method gets the index of an attribute given its uri and locanName.
209     * @param uri the URI of the attribute name.
210     * @param localName the local namer (after the ':' ) of the attribute name.
211     * @return the integer index of the attribute.
212     * @see org.xml.sax.Attributes#getIndex(String)
213     */
214    public final int getIndex(String uri, String localName)
215    {
216        int index;
217
218        if (super.getLength() < MAX)
219        {
220            // if we haven't got too many attributes let the
221            // super class look it up
222            index = super.getIndex(uri,localName);
223            return index;
224        }
225        // we have too many attributes and the super class is slow
226        // so find it quickly using our Map.
227        // Form the key of format "{uri}localName"
228        m_buff.setLength(0);
229        m_buff.append('{').append(uri).append('}').append(localName);
230        String key = m_buff.toString();
231        Integer i = m_indexFromQName.get(key);
232        if (i == null)
233            index = -1;
234        else
235            index = i;
236        return index;
237    }
238}
239