001// AttributesImpl.java - default implementation of Attributes.
002// Written by David Megginson, sax@megginson.com
003// NO WARRANTY!  This class is in the public domain.
004
005// $Id: AttributesImpl.java,v 1.1 2001/03/05 21:40:06 jstrachan Exp $
006
007
008package org.xml.sax.helpers;
009
010import org.xml.sax.Attributes;
011
012
013/**
014 * Default implementation of the Attributes interface.
015 *
016 * <blockquote>
017 * <em>This module, both source code and documentation, is in the
018 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
019 * </blockquote>
020 *
021 * <p>This class provides a default implementation of the SAX2
022 * {@link org.xml.sax.Attributes Attributes} interface, with the 
023 * addition of manipulators so that the list can be modified or 
024 * reused.</p>
025 *
026 * <p>There are two typical uses of this class:</p>
027 *
028 * <ol>
029 * <li>to take a persistent snapshot of an Attributes object
030 *  in a {@link org.xml.sax.ContentHandler#startElement startElement} event; or</li>
031 * <li>to construct or modify an Attributes object in a SAX2 driver or filter.</li>
032 * </ol>
033 *
034 * <p>This class replaces the now-deprecated SAX1 {@link 
035 * org.xml.sax.helpers.AttributeListImpl AttributeListImpl}
036 * class; in addition to supporting the updated Attributes
037 * interface rather than the deprecated {@link org.xml.sax.AttributeList
038 * AttributeList} interface, it also includes a much more efficient 
039 * implementation using a single array rather than a set of Vectors.</p>
040 *
041 * @since SAX 2.0
042 * @author David Megginson, 
043 *         <a href="mailto:sax@megginson.com">sax@megginson.com</a>
044 * @version 2.0
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 no memory is actually freed by this call:
324     * the current arrays are kept so that they can be 
325     * reused.</p>
326     */
327    public void clear ()
328    {
329        length = 0;
330    }
331
332
333    /**
334     * Copy an entire Attributes object.
335     *
336     * <p>It may be more efficient to reuse an existing object
337     * rather than constantly allocating new ones.</p>
338     * 
339     * @param atts The attributes to copy.
340     */
341    public void setAttributes (Attributes atts)
342    {
343        clear();
344        length = atts.getLength();
345        data = new String[length*5]; 
346        for (int i = 0; i < length; i++) {
347            data[i*5] = atts.getURI(i);
348            data[i*5+1] = atts.getLocalName(i);
349            data[i*5+2] = atts.getQName(i);
350            data[i*5+3] = atts.getType(i);
351            data[i*5+4] = atts.getValue(i);
352        }
353    }
354
355
356    /**
357     * Add an attribute to the end of the list.
358     *
359     * <p>For the sake of speed, this method does no checking
360     * to see if the attribute is already in the list: that is
361     * the responsibility of the application.</p>
362     *
363     * @param uri The Namespace URI, or the empty string if
364     *        none is available or Namespace processing is not
365     *        being performed.
366     * @param localName The local name, or the empty string if
367     *        Namespace processing is not being performed.
368     * @param qName The qualified (prefixed) name, or the empty string
369     *        if qualified names are not available.
370     * @param type The attribute type as a string.
371     * @param value The attribute value.
372     */
373    public void addAttribute (String uri, String localName, String qName,
374                              String type, String value)
375    {
376        ensureCapacity(length+1);
377        data[length*5] = uri;
378        data[length*5+1] = localName;
379        data[length*5+2] = qName;
380        data[length*5+3] = type;
381        data[length*5+4] = value;
382        length++;
383    }
384
385
386    /**
387     * Set an attribute in the list.
388     *
389     * <p>For the sake of speed, this method does no checking
390     * for name conflicts or well-formedness: such checks are the
391     * responsibility of the application.</p>
392     *
393     * @param index The index of the attribute (zero-based).
394     * @param uri The Namespace URI, or the empty string if
395     *        none is available or Namespace processing is not
396     *        being performed.
397     * @param localName The local name, or the empty string if
398     *        Namespace processing is not being performed.
399     * @param qName The qualified name, or the empty string
400     *        if qualified names are not available.
401     * @param type The attribute type as a string.
402     * @param value The attribute value.
403     * @exception java.lang.ArrayIndexOutOfBoundsException When the
404     *            supplied index does not point to an attribute
405     *            in the list.
406     */
407    public void setAttribute (int index, String uri, String localName,
408                              String qName, String type, String value)
409    {
410        if (index >= 0 && index < length) {
411            data[index*5] = uri;
412            data[index*5+1] = localName;
413            data[index*5+2] = qName;
414            data[index*5+3] = type;
415            data[index*5+4] = value;
416        } else {
417            badIndex(index);
418        }
419    }
420
421
422    /**
423     * Remove an attribute from the list.
424     *
425     * @param index The index of the attribute (zero-based).
426     * @exception java.lang.ArrayIndexOutOfBoundsException When the
427     *            supplied index does not point to an attribute
428     *            in the list.
429     */
430    public void removeAttribute (int index)
431    {
432        if (index >= 0 && index < length) {
433            data[index] = null;
434            if (index < length - 1) {
435                System.arraycopy(data, (index+1)*5, data, index*5,
436                                 (length-index)*5);
437            }
438            length--;
439        } else {
440            badIndex(index);
441        }
442    }
443
444
445    /**
446     * Set the Namespace URI of a specific attribute.
447     *
448     * @param index The index of the attribute (zero-based).
449     * @param uri The attribute's Namespace URI, or the empty
450     *        string for none.
451     * @exception java.lang.ArrayIndexOutOfBoundsException When the
452     *            supplied index does not point to an attribute
453     *            in the list.
454     */
455    public void setURI (int index, String uri)
456    {
457        if (index >= 0 && index < length) {
458            data[index*5] = uri;
459        } else {
460            badIndex(index);
461        }
462    }
463
464
465    /**
466     * Set the local name of a specific attribute.
467     *
468     * @param index The index of the attribute (zero-based).
469     * @param localName The attribute's local name, or the empty
470     *        string for none.
471     * @exception java.lang.ArrayIndexOutOfBoundsException When the
472     *            supplied index does not point to an attribute
473     *            in the list.
474     */
475    public void setLocalName (int index, String localName)
476    {
477        if (index >= 0 && index < length) {
478            data[index*5+1] = localName;
479        } else {
480            badIndex(index);
481        }
482    }
483
484
485    /**
486     * Set the qualified name of a specific attribute.
487     *
488     * @param index The index of the attribute (zero-based).
489     * @param qName The attribute's qualified name, or the empty
490     *        string for none.
491     * @exception java.lang.ArrayIndexOutOfBoundsException When the
492     *            supplied index does not point to an attribute
493     *            in the list.
494     */
495    public void setQName (int index, String qName)
496    {
497        if (index >= 0 && index < length) {
498            data[index*5+2] = qName;
499        } else {
500            badIndex(index);
501        }
502    }
503
504
505    /**
506     * Set the type of a specific attribute.
507     *
508     * @param index The index of the attribute (zero-based).
509     * @param type The attribute's type.
510     * @exception java.lang.ArrayIndexOutOfBoundsException When the
511     *            supplied index does not point to an attribute
512     *            in the list.
513     */
514    public void setType (int index, String type)
515    {
516        if (index >= 0 && index < length) {
517            data[index*5+3] = type;
518        } else {
519            badIndex(index);
520        }
521    }
522
523
524    /**
525     * Set the value of a specific attribute.
526     *
527     * @param index The index of the attribute (zero-based).
528     * @param value The attribute's value.
529     * @exception java.lang.ArrayIndexOutOfBoundsException When the
530     *            supplied index does not point to an attribute
531     *            in the list.
532     */
533    public void setValue (int index, String value)
534    {
535        if (index >= 0 && index < length) {
536            data[index*5+4] = value;
537        } else {
538            badIndex(index);
539        }
540    }
541
542
543
544    ////////////////////////////////////////////////////////////////////
545    // Internal methods.
546    ////////////////////////////////////////////////////////////////////
547
548
549    /**
550     * Ensure the internal array's capacity.
551     *
552     * @param n The minimum number of attributes that the array must
553     *        be able to hold.
554     */
555    private void ensureCapacity (int n)
556    {
557        if (n > 0 && data == null) {
558            data = new String[25];
559        }
560
561        int max = data.length;
562        if (max >= n * 5) {
563            return;
564        }
565
566
567        while (max < n * 5) {
568            max *= 2;
569        }
570        String newData[] = new String[max];
571        System.arraycopy(data, 0, newData, 0, length*5);
572        data = newData;
573    }
574
575
576    /**
577     * Report a bad array index in a manipulator.
578     *
579     * @param index The index to report.
580     * @exception java.lang.ArrayIndexOutOfBoundsException Always.
581     */
582    private void badIndex (int index)
583        throws ArrayIndexOutOfBoundsException
584    {
585        String msg =
586            "Attempt to modify attribute at illegal index: " + index;
587        throw new ArrayIndexOutOfBoundsException(msg);
588    }
589
590
591
592    ////////////////////////////////////////////////////////////////////
593    // Internal state.
594    ////////////////////////////////////////////////////////////////////
595
596    int length;
597    String data [];
598
599}
600
601// end of AttributesImpl.java
602