001// AttributesImpl.java - default implementation of Attributes.
002// http://www.saxproject.org
003// Written by David Megginson
004// NO WARRANTY!  This class is in the public domain.
005// $Id: AttributesImpl.java,v 1.1 2004/12/23 22:38:42 mark Exp $
006
007package org.xml.sax.helpers;
008
009import org.xml.sax.Attributes;
010
011
012/**
013 * Default implementation of the Attributes interface.
014 *
015 * <blockquote>
016 * <em>This module, both source code and documentation, is in the
017 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
018 * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
019 * for further information.
020 * </blockquote>
021 *
022 * <p>This class provides a default implementation of the SAX2
023 * {@link org.xml.sax.Attributes Attributes} interface, with the 
024 * addition of manipulators so that the list can be modified or 
025 * reused.</p>
026 *
027 * <p>There are two typical uses of this class:</p>
028 *
029 * <ol>
030 * <li>to take a persistent snapshot of an Attributes object
031 *  in a {@link org.xml.sax.ContentHandler#startElement startElement} event; or</li>
032 * <li>to construct or modify an Attributes object in a SAX2 driver or filter.</li>
033 * </ol>
034 *
035 * <p>This class replaces the now-deprecated SAX1 {@link 
036 * org.xml.sax.helpers.AttributeListImpl AttributeListImpl}
037 * class; in addition to supporting the updated Attributes
038 * interface rather than the deprecated {@link org.xml.sax.AttributeList
039 * AttributeList} interface, it also includes a much more efficient 
040 * implementation using a single array rather than a set of Vectors.</p>
041 *
042 * @since SAX 2.0
043 * @author David Megginson
044 * @version 2.0.1 (sax2r2)
045 */
046public class AttributesImpl implements Attributes
047{
048
049
050    ////////////////////////////////////////////////////////////////////
051    // Constructors.
052    ////////////////////////////////////////////////////////////////////
053
054
055    /**
056     * Construct a new, empty AttributesImpl object.
057     */
058    public AttributesImpl ()
059    {
060        length = 0;
061        data = null;
062    }
063
064
065    /**
066     * Copy an existing Attributes object.
067     *
068     * <p>This constructor is especially useful inside a
069     * {@link org.xml.sax.ContentHandler#startElement startElement} event.</p>
070     *
071     * @param atts The existing Attributes object.
072     */
073    public AttributesImpl (Attributes atts)
074    {
075        setAttributes(atts);
076    }
077
078
079
080    ////////////////////////////////////////////////////////////////////
081    // Implementation of org.xml.sax.Attributes.
082    ////////////////////////////////////////////////////////////////////
083
084
085    /**
086     * Return the number of attributes in the list.
087     *
088     * @return The number of attributes in the list.
089     * @see org.xml.sax.Attributes#getLength
090     */
091    public int getLength ()
092    {
093        return length;
094    }
095
096
097    /**
098     * Return an attribute's Namespace URI.
099     *
100     * @param index The attribute's index (zero-based).
101     * @return The Namespace URI, the empty string if none is
102     *         available, or null if the index is out of range.
103     * @see org.xml.sax.Attributes#getURI
104     */
105    public String getURI (int index)
106    {
107        if (index >= 0 && index < length) {
108            return data[index*5];
109        } else {
110            return null;
111        }
112    }
113
114
115    /**
116     * Return an attribute's local name.
117     *
118     * @param index The attribute's index (zero-based).
119     * @return The attribute's local name, the empty string if 
120     *         none is available, or null if the index if out of range.
121     * @see org.xml.sax.Attributes#getLocalName
122     */
123    public String getLocalName (int index)
124    {
125        if (index >= 0 && index < length) {
126            return data[index*5+1];
127        } else {
128            return null;
129        }
130    }
131
132
133    /**
134     * Return an attribute's qualified (prefixed) name.
135     *
136     * @param index The attribute's index (zero-based).
137     * @return The attribute's qualified name, the empty string if 
138     *         none is available, or null if the index is out of bounds.
139     * @see org.xml.sax.Attributes#getQName
140     */
141    public String getQName (int index)
142    {
143        if (index >= 0 && index < length) {
144            return data[index*5+2];
145        } else {
146            return null;
147        }
148    }
149
150
151    /**
152     * Return an attribute's type by index.
153     *
154     * @param index The attribute's index (zero-based).
155     * @return The attribute's type, "CDATA" if the type is unknown, or null
156     *         if the index is out of bounds.
157     * @see org.xml.sax.Attributes#getType(int)
158     */
159    public String getType (int index)
160    {
161        if (index >= 0 && index < length) {
162            return data[index*5+3];
163        } else {
164            return null;
165        }
166    }
167
168
169    /**
170     * Return an attribute's value by index.
171     *
172     * @param index The attribute's index (zero-based).
173     * @return The attribute's value or null if the index is out of bounds.
174     * @see org.xml.sax.Attributes#getValue(int)
175     */
176    public String getValue (int index)
177    {
178        if (index >= 0 && index < length) {
179            return data[index*5+4];
180        } else {
181            return null;
182        }
183    }
184
185
186    /**
187     * Look up an attribute's index by Namespace name.
188     *
189     * <p>In many cases, it will be more efficient to look up the name once and
190     * use the index query methods rather than using the name query methods
191     * repeatedly.</p>
192     *
193     * @param uri The attribute's Namespace URI, or the empty
194     *        string if none is available.
195     * @param localName The attribute's local name.
196     * @return The attribute's index, or -1 if none matches.
197     * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
198     */
199    public int getIndex (String uri, String localName)
200    {
201        int max = length * 5;
202        for (int i = 0; i < max; i += 5) {
203            if (data[i].equals(uri) && data[i+1].equals(localName)) {
204                return i / 5;
205            }
206        } 
207        return -1;
208    }
209
210
211    /**
212     * Look up an attribute's index by qualified (prefixed) name.
213     *
214     * @param qName The qualified name.
215     * @return The attribute's index, or -1 if none matches.
216     * @see org.xml.sax.Attributes#getIndex(java.lang.String)
217     */
218    public int getIndex (String qName)
219    {
220        int max = length * 5;
221        for (int i = 0; i < max; i += 5) {
222            if (data[i+2].equals(qName)) {
223                return i / 5;
224            }
225        } 
226        return -1;
227    }
228
229
230    /**
231     * Look up an attribute's type by Namespace-qualified name.
232     *
233     * @param uri The Namespace URI, or the empty string for a name
234     *        with no explicit Namespace URI.
235     * @param localName The local name.
236     * @return The attribute's type, or null if there is no
237     *         matching attribute.
238     * @see org.xml.sax.Attributes#getType(java.lang.String,java.lang.String)
239     */
240    public String getType (String uri, String localName)
241    {
242        int max = length * 5;
243        for (int i = 0; i < max; i += 5) {
244            if (data[i].equals(uri) && data[i+1].equals(localName)) {
245                return data[i+3];
246            }
247        } 
248        return null;
249    }
250
251
252    /**
253     * Look up an attribute's type by qualified (prefixed) name.
254     *
255     * @param qName The qualified name.
256     * @return The attribute's type, or null if there is no
257     *         matching attribute.
258     * @see org.xml.sax.Attributes#getType(java.lang.String)
259     */
260    public String getType (String qName)
261    {
262        int max = length * 5;
263        for (int i = 0; i < max; i += 5) {
264            if (data[i+2].equals(qName)) {
265                return data[i+3];
266            }
267        }
268        return null;
269    }
270
271
272    /**
273     * Look up an attribute's value by Namespace-qualified name.
274     *
275     * @param uri The Namespace URI, or the empty string for a name
276     *        with no explicit Namespace URI.
277     * @param localName The local name.
278     * @return The attribute's value, or null if there is no
279     *         matching attribute.
280     * @see org.xml.sax.Attributes#getValue(java.lang.String,java.lang.String)
281     */
282    public String getValue (String uri, String localName)
283    {
284        int max = length * 5;
285        for (int i = 0; i < max; i += 5) {
286            if (data[i].equals(uri) && data[i+1].equals(localName)) {
287                return data[i+4];
288            }
289        }
290        return null;
291    }
292
293
294    /**
295     * Look up an attribute's value by qualified (prefixed) name.
296     *
297     * @param qName The qualified name.
298     * @return The attribute's value, or null if there is no
299     *         matching attribute.
300     * @see org.xml.sax.Attributes#getValue(java.lang.String)
301     */
302    public String getValue (String qName)
303    {
304        int max = length * 5;
305        for (int i = 0; i < max; i += 5) {
306            if (data[i+2].equals(qName)) {
307                return data[i+4];
308            }
309        }
310        return null;
311    }
312
313
314
315    ////////////////////////////////////////////////////////////////////
316    // Manipulators.
317    ////////////////////////////////////////////////////////////////////
318
319
320    /**
321     * Clear the attribute list for reuse.
322     *
323     * <p>Note that little memory is freed by this call:
324     * the current array is kept so it can be 
325     * reused.</p>
326     */
327    public void clear ()
328    {
329        if (data != null) {
330            for (int i = 0; i < (length * 5); i++)
331                data [i] = null;
332        }
333        length = 0;
334    }
335
336
337    /**
338     * Copy an entire Attributes object.
339     *
340     * <p>It may be more efficient to reuse an existing object
341     * rather than constantly allocating new ones.</p>
342     * 
343     * @param atts The attributes to copy.
344     */
345    public void setAttributes (Attributes atts)
346    {
347        clear();
348        length = atts.getLength();
349        if (length > 0) {
350            data = new String[length*5];
351            for (int i = 0; i < length; i++) {
352                data[i*5] = atts.getURI(i);
353                data[i*5+1] = atts.getLocalName(i);
354                data[i*5+2] = atts.getQName(i);
355                data[i*5+3] = atts.getType(i);
356                data[i*5+4] = atts.getValue(i);
357            }
358        }
359    }
360
361
362    /**
363     * Add an attribute to the end of the list.
364     *
365     * <p>For the sake of speed, this method does no checking
366     * to see if the attribute is already in the list: that is
367     * the responsibility of the application.</p>
368     *
369     * @param uri The Namespace URI, or the empty string if
370     *        none is available or Namespace processing is not
371     *        being performed.
372     * @param localName The local name, or the empty string if
373     *        Namespace processing is not being performed.
374     * @param qName The qualified (prefixed) name, or the empty string
375     *        if qualified names are not available.
376     * @param type The attribute type as a string.
377     * @param value The attribute value.
378     */
379    public void addAttribute (String uri, String localName, String qName,
380                              String type, String value)
381    {
382        ensureCapacity(length+1);
383        data[length*5] = uri;
384        data[length*5+1] = localName;
385        data[length*5+2] = qName;
386        data[length*5+3] = type;
387        data[length*5+4] = value;
388        length++;
389    }
390
391
392    /**
393     * Set an attribute in the list.
394     *
395     * <p>For the sake of speed, this method does no checking
396     * for name conflicts or well-formedness: such checks are the
397     * responsibility of the application.</p>
398     *
399     * @param index The index of the attribute (zero-based).
400     * @param uri The Namespace URI, or the empty string if
401     *        none is available or Namespace processing is not
402     *        being performed.
403     * @param localName The local name, or the empty string if
404     *        Namespace processing is not being performed.
405     * @param qName The qualified name, or the empty string
406     *        if qualified names are not available.
407     * @param type The attribute type as a string.
408     * @param value The attribute value.
409     * @exception java.lang.ArrayIndexOutOfBoundsException When the
410     *            supplied index does not point to an attribute
411     *            in the list.
412     */
413    public void setAttribute (int index, String uri, String localName,
414                              String qName, String type, String value)
415    {
416        if (index >= 0 && index < length) {
417            data[index*5] = uri;
418            data[index*5+1] = localName;
419            data[index*5+2] = qName;
420            data[index*5+3] = type;
421            data[index*5+4] = value;
422        } else {
423            badIndex(index);
424        }
425    }
426
427
428    /**
429     * Remove an attribute from the list.
430     *
431     * @param index The index of the attribute (zero-based).
432     * @exception java.lang.ArrayIndexOutOfBoundsException When the
433     *            supplied index does not point to an attribute
434     *            in the list.
435     */
436    public void removeAttribute (int index)
437    {
438        if (index >= 0 && index < length) {
439            if (index < length - 1) {
440                System.arraycopy(data, (index+1)*5, data, index*5,
441                                 (length-index-1)*5);
442            }
443            index = (length - 1) * 5;
444            data [index++] = null;
445            data [index++] = null;
446            data [index++] = null;
447            data [index++] = null;
448            data [index] = null;
449            length--;
450        } else {
451            badIndex(index);
452        }
453    }
454
455
456    /**
457     * Set the Namespace URI of a specific attribute.
458     *
459     * @param index The index of the attribute (zero-based).
460     * @param uri The attribute's Namespace URI, or the empty
461     *        string for none.
462     * @exception java.lang.ArrayIndexOutOfBoundsException When the
463     *            supplied index does not point to an attribute
464     *            in the list.
465     */
466    public void setURI (int index, String uri)
467    {
468        if (index >= 0 && index < length) {
469            data[index*5] = uri;
470        } else {
471            badIndex(index);
472        }
473    }
474
475
476    /**
477     * Set the local name of a specific attribute.
478     *
479     * @param index The index of the attribute (zero-based).
480     * @param localName The attribute's local name, or the empty
481     *        string for none.
482     * @exception java.lang.ArrayIndexOutOfBoundsException When the
483     *            supplied index does not point to an attribute
484     *            in the list.
485     */
486    public void setLocalName (int index, String localName)
487    {
488        if (index >= 0 && index < length) {
489            data[index*5+1] = localName;
490        } else {
491            badIndex(index);
492        }
493    }
494
495
496    /**
497     * Set the qualified name of a specific attribute.
498     *
499     * @param index The index of the attribute (zero-based).
500     * @param qName The attribute's qualified name, or the empty
501     *        string for none.
502     * @exception java.lang.ArrayIndexOutOfBoundsException When the
503     *            supplied index does not point to an attribute
504     *            in the list.
505     */
506    public void setQName (int index, String qName)
507    {
508        if (index >= 0 && index < length) {
509            data[index*5+2] = qName;
510        } else {
511            badIndex(index);
512        }
513    }
514
515
516    /**
517     * Set the type of a specific attribute.
518     *
519     * @param index The index of the attribute (zero-based).
520     * @param type The attribute's type.
521     * @exception java.lang.ArrayIndexOutOfBoundsException When the
522     *            supplied index does not point to an attribute
523     *            in the list.
524     */
525    public void setType (int index, String type)
526    {
527        if (index >= 0 && index < length) {
528            data[index*5+3] = type;
529        } else {
530            badIndex(index);
531        }
532    }
533
534
535    /**
536     * Set the value of a specific attribute.
537     *
538     * @param index The index of the attribute (zero-based).
539     * @param value The attribute's value.
540     * @exception java.lang.ArrayIndexOutOfBoundsException When the
541     *            supplied index does not point to an attribute
542     *            in the list.
543     */
544    public void setValue (int index, String value)
545    {
546        if (index >= 0 && index < length) {
547            data[index*5+4] = value;
548        } else {
549            badIndex(index);
550        }
551    }
552
553
554
555    ////////////////////////////////////////////////////////////////////
556    // Internal methods.
557    ////////////////////////////////////////////////////////////////////
558
559
560    /**
561     * Ensure the internal array's capacity.
562     *
563     * @param n The minimum number of attributes that the array must
564     *        be able to hold.
565     */
566    private void ensureCapacity (int n)    {
567        if (n <= 0) {
568            return;
569        }
570        int max;
571        if (data == null || data.length == 0) {
572            max = 25;
573        }
574        else if (data.length >= n * 5) {
575            return;
576        }
577        else {
578            max = data.length;
579        }
580        while (max < n * 5) {
581            max *= 2;
582        }
583
584        String newData[] = new String[max];
585        if (length > 0) {
586            System.arraycopy(data, 0, newData, 0, length*5);
587        }
588        data = newData;
589    }
590
591
592    /**
593     * Report a bad array index in a manipulator.
594     *
595     * @param index The index to report.
596     * @exception java.lang.ArrayIndexOutOfBoundsException Always.
597     */
598    private void badIndex (int index)
599        throws ArrayIndexOutOfBoundsException
600    {
601        String msg =
602            "Attempt to modify attribute at illegal index: " + index;
603        throw new ArrayIndexOutOfBoundsException(msg);
604    }
605
606
607
608    ////////////////////////////////////////////////////////////////////
609    // Internal state.
610    ////////////////////////////////////////////////////////////////////
611
612    int length;
613    String data [];
614
615}
616
617// end of AttributesImpl.java
618