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    
007    package org.xml.sax.helpers;
008    
009    import 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     */
046    public 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