001: /*--
002:
003: $Id: Attribute.java,v 1.1 2005/04/27 09:32:37 wittek Exp $
004:
005: Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
006: All rights reserved.
007:
008: Redistribution and use in source and binary forms, with or without
009: modification, are permitted provided that the following conditions
010: are met:
011:
012: 1. Redistributions of source code must retain the above copyright
013: notice, this list of conditions, and the following disclaimer.
014:
015: 2. Redistributions in binary form must reproduce the above copyright
016: notice, this list of conditions, and the disclaimer that follows
017: these conditions in the documentation and/or other materials
018: provided with the distribution.
019:
020: 3. The name "JDOM" must not be used to endorse or promote products
021: derived from this software without prior written permission. For
022: written permission, please contact <request_AT_jdom_DOT_org>.
023:
024: 4. Products derived from this software may not be called "JDOM", nor
025: may "JDOM" appear in their name, without prior written permission
026: from the JDOM Project Management <request_AT_jdom_DOT_org>.
027:
028: In addition, we request (but do not require) that you include in the
029: end-user documentation provided with the redistribution and/or in the
030: software itself an acknowledgement equivalent to the following:
031: "This product includes software developed by the
032: JDOM Project (http://www.jdom.org/)."
033: Alternatively, the acknowledgment may be graphical using the logos
034: available at http://www.jdom.org/images/logos.
035:
036: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
040: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: SUCH DAMAGE.
048:
049: This software consists of voluntary contributions made by many
050: individuals on behalf of the JDOM Project and was originally
051: created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
052: Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
053: on the JDOM Project, please see <http://www.jdom.org/>.
054:
055: */
056:
057: package org.jdom;
058:
059: import java.io.*;
060:
061: /**
062: * An XML attribute. Methods allow the user to obtain the value of the attribute
063: * as well as namespace and type information.
064: *
065: * @version $Revision: 1.1 $, $Date: 2005/04/27 09:32:37 $
066: * @author Brett McLaughlin
067: * @author Jason Hunter
068: * @author Elliotte Rusty Harold
069: * @author Wesley Biggs
070: */
071: public class Attribute implements Serializable, Cloneable {
072:
073: private static final String CVS_ID = "@(#) $RCSfile: Attribute.java,v $ $Revision: 1.1 $ $Date: 2005/04/27 09:32:37 $ $Name: $";
074:
075: /**
076: * Attribute type: the attribute has not been declared or type
077: * is unknown.
078: *
079: * @see #getAttributeType
080: */
081: public final static int UNDECLARED_TYPE = 0;
082:
083: /**
084: * Attribute type: the attribute value is a string.
085: *
086: * @see #getAttributeType
087: */
088: public final static int CDATA_TYPE = 1;
089:
090: /**
091: * Attribute type: the attribute value is a unique identifier.
092: *
093: * @see #getAttributeType
094: */
095: public final static int ID_TYPE = 2;
096:
097: /**
098: * Attribute type: the attribute value is a reference to a
099: * unique identifier.
100: *
101: * @see #getAttributeType
102: */
103: public final static int IDREF_TYPE = 3;
104:
105: /**
106: * Attribute type: the attribute value is a list of references to
107: * unique identifiers.
108: *
109: * @see #getAttributeType
110: */
111: public final static int IDREFS_TYPE = 4;
112:
113: /**
114: * Attribute type: the attribute value is the name of an entity.
115: *
116: * @see #getAttributeType
117: */
118: public final static int ENTITY_TYPE = 5;
119:
120: /**
121: * <p>
122: * Attribute type: the attribute value is a list of entity names.
123: * </p>
124: *
125: * @see #getAttributeType
126: */
127: public final static int ENTITIES_TYPE = 6;
128:
129: /**
130: * Attribute type: the attribute value is a name token.
131: * <p>
132: * According to SAX 2.0 specification, attributes of enumerated
133: * types should be reported as "NMTOKEN" by SAX parsers. But the
134: * major parsers (Xerces and Crimson) provide specific values
135: * that permit to recognize them as {@link #ENUMERATED_TYPE}.
136: *
137: * @see #getAttributeType
138: */
139: public final static int NMTOKEN_TYPE = 7;
140:
141: /**
142: * Attribute type: the attribute value is a list of name tokens.
143: *
144: * @see #getAttributeType
145: */
146: public final static int NMTOKENS_TYPE = 8;
147:
148: /**
149: * Attribute type: the attribute value is the name of a notation.
150: *
151: * @see #getAttributeType
152: */
153: public final static int NOTATION_TYPE = 9;
154:
155: /**
156: * Attribute type: the attribute value is a name token from an
157: * enumeration.
158: *
159: * @see #getAttributeType
160: */
161: public final static int ENUMERATED_TYPE = 10;
162:
163: // Keep the old constant names for one beta cycle to help migration
164:
165: /** The local name of the <code>Attribute</code> */
166: protected String name;
167:
168: /** The <code>{@link Namespace}</code> of the <code>Attribute</code> */
169: protected transient Namespace namespace;
170:
171: /** The value of the <code>Attribute</code> */
172: protected String value;
173:
174: /** The type of the <code>Attribute</code> */
175: protected int type = UNDECLARED_TYPE;
176:
177: /** Parent element, or null if none */
178: protected Object parent;
179:
180: /**
181: * Default, no-args constructor for implementations to use if needed.
182: */
183: protected Attribute() {
184: }
185:
186: /**
187: * This will create a new <code>Attribute</code> with the
188: * specified (local) name and value, and in the provided
189: * <code>{@link Namespace}</code>.
190: *
191: * @param name <code>String</code> name of <code>Attribute</code>.
192: * @param value <code>String</code> value for new attribute.
193: * @param namespace <code>Namespace</code> namespace for new attribute.
194: * @throws IllegalNameException if the given name is illegal as an
195: * attribute name or if if the new namespace is the default
196: * namespace. Attributes cannot be in a default namespace.
197: * @throws IllegalDataException if the given attribute value is
198: * illegal character data (as determined by
199: * {@link org.jdom.Verifier#checkCharacterData}).
200: */
201: public Attribute(String name, String value, Namespace namespace) {
202: setName(name);
203: setValue(value);
204: setNamespace(namespace);
205: }
206:
207: /**
208: * This will create a new <code>Attribute</code> with the
209: * specified (local) name, value, and type, and in the provided
210: * <code>{@link Namespace}</code>.
211: *
212: * @param name <code>String</code> name of <code>Attribute</code>.
213: * @param value <code>String</code> value for new attribute.
214: * @param type <code>int</code> type for new attribute.
215: * @param namespace <code>Namespace</code> namespace for new attribute.
216: * @throws IllegalNameException if the given name is illegal as an
217: * attribute name or if if the new namespace is the default
218: * namespace. Attributes cannot be in a default namespace.
219: * @throws IllegalDataException if the given attribute value is
220: * illegal character data (as determined by
221: * {@link org.jdom.Verifier#checkCharacterData}) or
222: * if the given attribute type is not one of the
223: * supported types.
224: */
225: public Attribute(String name, String value, int type,
226: Namespace namespace) {
227: setName(name);
228: setValue(value);
229: setAttributeType(type);
230: setNamespace(namespace);
231: }
232:
233: /**
234: * This will create a new <code>Attribute</code> with the
235: * specified (local) name and value, and does not place
236: * the attribute in a <code>{@link Namespace}</code>.
237: * <p>
238: * <b>Note</b>: This actually explicitly puts the
239: * <code>Attribute</code> in the "empty" <code>Namespace</code>
240: * (<code>{@link Namespace#NO_NAMESPACE}</code>).
241: *
242: * @param name <code>String</code> name of <code>Attribute</code>.
243: * @param value <code>String</code> value for new attribute.
244: * @throws IllegalNameException if the given name is illegal as an
245: * attribute name.
246: * @throws IllegalDataException if the given attribute value is
247: * illegal character data (as determined by
248: * {@link org.jdom.Verifier#checkCharacterData}).
249: */
250: public Attribute(String name, String value) {
251: this (name, value, UNDECLARED_TYPE, Namespace.NO_NAMESPACE);
252: }
253:
254: /**
255: * This will create a new <code>Attribute</code> with the
256: * specified (local) name, value and type, and does not place
257: * the attribute in a <code>{@link Namespace}</code>.
258: * <p>
259: * <b>Note</b>: This actually explicitly puts the
260: * <code>Attribute</code> in the "empty" <code>Namespace</code>
261: * (<code>{@link Namespace#NO_NAMESPACE}</code>).
262: *
263: * @param name <code>String</code> name of <code>Attribute</code>.
264: * @param value <code>String</code> value for new attribute.
265: * @param type <code>int</code> type for new attribute.
266: * @throws IllegalNameException if the given name is illegal as an
267: * attribute name.
268: * @throws IllegalDataException if the given attribute value is
269: * illegal character data (as determined by
270: * {@link org.jdom.Verifier#checkCharacterData}) or
271: * if the given attribute type is not one of the
272: * supported types.
273: */
274: public Attribute(String name, String value, int type) {
275: this (name, value, type, Namespace.NO_NAMESPACE);
276: }
277:
278: /**
279: * This will return the parent of this <code>Attribute</code>.
280: * If there is no parent, then this returns <code>null</code>.
281: *
282: * @return parent of this <code>Attribute</code>
283: */
284: public Element getParent() {
285: return (Element) parent;
286: }
287:
288: /**
289: * This retrieves the owning <code>{@link Document}</code> for
290: * this Attribute, or null if not a currently a member of a
291: * <code>{@link Document}</code>.
292: *
293: * @return <code>Document</code> owning this Attribute, or null.
294: */
295: public Document getDocument() {
296: if (parent != null) {
297: return ((Element) parent).getDocument();
298: }
299: return null;
300: }
301:
302: /**
303: * This will set the parent of this <code>Attribute</code>.
304: *
305: * @param parent <code>Element</code> to be new parent.
306: * @return this <code>Attribute</code> modified.
307: */
308: protected Attribute setParent(Element parent) {
309: this .parent = parent;
310: return this ;
311: }
312:
313: /**
314: * This detaches the <code>Attribute</code> from its parent, or does
315: * nothing if the <code>Attribute</code> has no parent.
316: *
317: * @return <code>Attribute</code> - this <code>Attribute</code> modified.
318: */
319: public Attribute detach() {
320: Element p = getParent();
321: if (p != null) {
322: p.removeAttribute(this .getName(), this .getNamespace());
323: }
324: return this ;
325: }
326:
327: /**
328: * This will retrieve the local name of the
329: * <code>Attribute</code>. For any XML attribute
330: * which appears as
331: * <code>[namespacePrefix]:[attributeName]</code>,
332: * the local name of the attribute would be
333: * <code>[attributeName]</code>. When the attribute
334: * has no namespace, the local name is simply the attribute
335: * name.
336: * <p>
337: * To obtain the namespace prefix for this
338: * attribute, the
339: * <code>{@link #getNamespacePrefix()}</code>
340: * method should be used.
341: *
342: * @return <code>String</code> - name of this attribute,
343: * without any namespace prefix.
344: */
345: public String getName() {
346: return name;
347: }
348:
349: /**
350: * This sets the local name of the <code>Attribute</code>.
351: *
352: * @param name the new local name to set
353: * @return <code>Attribute</code> - the attribute modified.
354: * @throws IllegalNameException if the given name is illegal as an
355: * attribute name.
356: */
357: public Attribute setName(String name) {
358: String reason;
359: if ((reason = Verifier.checkAttributeName(name)) != null) {
360: throw new IllegalNameException(name, "attribute", reason);
361: }
362: this .name = name;
363: return this ;
364: }
365:
366: /**
367: * This will retrieve the qualified name of the <code>Attribute</code>.
368: * For any XML attribute whose name is
369: * <code>[namespacePrefix]:[elementName]</code>,
370: * the qualified name of the attribute would be
371: * everything (both namespace prefix and
372: * element name). When the attribute has no
373: * namespace, the qualified name is simply the attribute's
374: * local name.
375: * <p>
376: * To obtain the local name of the attribute, the
377: * <code>{@link #getName()}</code> method should be used.
378: * <p>
379: * To obtain the namespace prefix for this attribute,
380: * the <code>{@link #getNamespacePrefix()}</code>
381: * method should be used.
382: *
383: * @return <code>String</code> - full name for this element.
384: */
385: public String getQualifiedName() {
386: // Note: Any changes here should be reflected in
387: // XMLOutputter.printQualifiedName()
388: String prefix = namespace.getPrefix();
389: if ((prefix != null) && (!prefix.equals(""))) {
390: return new StringBuffer(prefix).append(':').append(
391: getName()).toString();
392: } else {
393: return getName();
394: }
395: }
396:
397: /**
398: * This will retrieve the namespace prefix of the
399: * <code>Attribute</code>. For any XML attribute
400: * which appears as
401: * <code>[namespacePrefix]:[attributeName]</code>,
402: * the namespace prefix of the attribute would be
403: * <code>[namespacePrefix]</code>. When the attribute
404: * has no namespace, an empty <code>String</code> is returned.
405: *
406: * @return <code>String</code> - namespace prefix of this
407: * attribute.
408: */
409: public String getNamespacePrefix() {
410: return namespace.getPrefix();
411: }
412:
413: /**
414: * This returns the URI mapped to this <code>Attribute</code>'s
415: * prefix. If no mapping is found, an empty <code>String</code> is
416: * returned.
417: *
418: * @return <code>String</code> - namespace URI for this <code>Attribute</code>.
419: */
420: public String getNamespaceURI() {
421: return namespace.getURI();
422: }
423:
424: /**
425: * This will return this <code>Attribute</code>'s
426: * <code>{@link Namespace}</code>.
427: *
428: * @return <code>Namespace</code> - Namespace object for this <code>Attribute</code>
429: */
430: public Namespace getNamespace() {
431: return namespace;
432: }
433:
434: /**
435: * This sets this <code>Attribute</code>'s <code>{@link Namespace}</code>.
436: * If the provided namespace is null, the attribute will have no namespace.
437: * The namespace must have a prefix.
438: *
439: * @param namespace the new namespace
440: * @return <code>Element</code> - the element modified.
441: * @throws IllegalNameException if the new namespace is the default
442: * namespace. Attributes cannot be in a default namespace.
443: */
444: public Attribute setNamespace(Namespace namespace) {
445: if (namespace == null) {
446: namespace = Namespace.NO_NAMESPACE;
447: }
448:
449: // Verify the attribute isn't trying to be in a default namespace
450: // Attributes can't be in a default namespace
451: if (namespace != Namespace.NO_NAMESPACE
452: && namespace.getPrefix().equals("")) {
453: throw new IllegalNameException("", "attribute namespace",
454: "An attribute namespace without a prefix can only be the "
455: + "NO_NAMESPACE namespace");
456: }
457: this .namespace = namespace;
458: return this ;
459: }
460:
461: /**
462: * This will return the actual textual value of this
463: * <code>Attribute</code>. This will include all text
464: * within the quotation marks.
465: *
466: * @return <code>String</code> - value for this attribute.
467: */
468: public String getValue() {
469: return value;
470: }
471:
472: /**
473: * This will set the value of the <code>Attribute</code>.
474: *
475: * @param value <code>String</code> value for the attribute.
476: * @return <code>Attribute</code> - this Attribute modified.
477: * @throws IllegalDataException if the given attribute value is
478: * illegal character data (as determined by
479: * {@link org.jdom.Verifier#checkCharacterData}).
480: */
481: public Attribute setValue(String value) {
482: String reason = null;
483: if ((reason = Verifier.checkCharacterData(value)) != null) {
484: throw new IllegalDataException(value, "attribute", reason);
485: }
486: this .value = value;
487: return this ;
488: }
489:
490: /**
491: * This will return the actual declared type of this
492: * <code>Attribute</code>.
493: *
494: * @return <code>int</code> - type for this attribute.
495: */
496: public int getAttributeType() {
497: return type;
498: }
499:
500: /**
501: * This will set the type of the <code>Attribute</code>.
502: *
503: * @param type <code>int</code> type for the attribute.
504: * @return <code>Attribute</code> - this Attribute modified.
505: * @throws IllegalDataException if the given attribute type is
506: * not one of the supported types.
507: */
508: public Attribute setAttributeType(int type) {
509: if ((type < UNDECLARED_TYPE) || (type > ENUMERATED_TYPE)) {
510: throw new IllegalDataException(String.valueOf(type),
511: "attribute", "Illegal attribute type");
512: }
513: this .type = type;
514: return this ;
515: }
516:
517: /**
518: * This returns a <code>String</code> representation of the
519: * <code>Attribute</code>, suitable for debugging.
520: *
521: * @return <code>String</code> - information about the
522: * <code>Attribute</code>
523: */
524: public String toString() {
525: return new StringBuffer().append("[Attribute: ").append(
526: getQualifiedName()).append("=\"").append(value).append(
527: "\"").append("]").toString();
528: }
529:
530: /**
531: * This tests for equality of this <code>Attribute</code> to the supplied
532: * <code>Object</code>.
533: *
534: * @param ob <code>Object</code> to compare to.
535: * @return <code>boolean</code> - whether the <code>Attribute</code> is
536: * equal to the supplied <code>Object</code>.
537: */
538: public final boolean equals(Object ob) {
539: return (ob == this );
540: }
541:
542: /**
543: * This returns the hash code for this <code>Attribute</code>.
544: *
545: * @return <code>int</code> - hash code.
546: */
547: public final int hashCode() {
548: return super .hashCode();
549: }
550:
551: /**
552: * This will return a clone of this <code>Attribute</code>.
553: *
554: * @return <code>Object</code> - clone of this <code>Attribute</code>.
555: */
556: public Object clone() {
557: Attribute attribute = null;
558:
559: try {
560: attribute = (Attribute) super .clone();
561: } catch (CloneNotSupportedException ce) {
562: // Won't happen
563: }
564:
565: // Name, namespace, and value are references to imutable objects
566: // and are copied by super.clone() (aka Object.clone())
567:
568: // super.clone() copies reference to set parent to null
569: attribute.parent = null;
570: return attribute;
571: }
572:
573: /////////////////////////////////////////////////////////////////
574: // Convenience Methods below here
575: /////////////////////////////////////////////////////////////////
576:
577: /**
578: * This gets the value of the attribute, in
579: * <code>int</code> form, and if no conversion
580: * can occur, throws a
581: * <code>{@link DataConversionException}</code>
582: *
583: * @return <code>int</code> value of attribute.
584: * @throws DataConversionException when conversion fails.
585: */
586: public int getIntValue() throws DataConversionException {
587: try {
588: return Integer.parseInt(value.trim());
589: } catch (NumberFormatException e) {
590: throw new DataConversionException(name, "int");
591: }
592: }
593:
594: /**
595: * This gets the value of the attribute, in
596: * <code>long</code> form, and if no conversion
597: * can occur, throws a
598: * <code>{@link DataConversionException}</code>
599: *
600: * @return <code>long</code> value of attribute.
601: * @throws DataConversionException when conversion fails.
602: */
603: public long getLongValue() throws DataConversionException {
604: try {
605: return Long.parseLong(value.trim());
606: } catch (NumberFormatException e) {
607: throw new DataConversionException(name, "long");
608: }
609: }
610:
611: /**
612: * This gets the value of the attribute, in
613: * <code>float</code> form, and if no conversion
614: * can occur, throws a
615: * <code>{@link DataConversionException}</code>
616: *
617: * @return <code>float</code> value of attribute.
618: * @throws DataConversionException when conversion fails.
619: */
620: public float getFloatValue() throws DataConversionException {
621: try {
622: // Avoid Float.parseFloat() to support JDK 1.1
623: return Float.valueOf(value.trim()).floatValue();
624: } catch (NumberFormatException e) {
625: throw new DataConversionException(name, "float");
626: }
627: }
628:
629: /**
630: * This gets the value of the attribute, in
631: * <code>double</code> form, and if no conversion
632: * can occur, throws a
633: * <code>{@link DataConversionException}</code>
634: *
635: * @return <code>double</code> value of attribute.
636: * @throws DataConversionException when conversion fails.
637: */
638: public double getDoubleValue() throws DataConversionException {
639: try {
640: // Avoid Double.parseDouble() to support JDK 1.1
641: return Double.valueOf(value.trim()).doubleValue();
642: } catch (NumberFormatException e) {
643: throw new DataConversionException(name, "double");
644: }
645: }
646:
647: /**
648: * This gets the effective boolean value of the attribute, or throws a
649: * <code>{@link DataConversionException}</code> if a conversion can't be
650: * performed. True values are: "true", "on", "1", and "yes". False
651: * values are: "false", "off", "0", and "no". Values are trimmed before
652: * comparison. Values other than those listed here throw the exception.
653: *
654: * @return <code>boolean</code> value of attribute.
655: * @throws DataConversionException when conversion fails.
656: */
657: public boolean getBooleanValue() throws DataConversionException {
658: String valueTrim = value.trim();
659: if ((valueTrim.equalsIgnoreCase("true"))
660: || (valueTrim.equalsIgnoreCase("on"))
661: || (valueTrim.equalsIgnoreCase("1"))
662: || (valueTrim.equalsIgnoreCase("yes"))) {
663: return true;
664: } else if ((valueTrim.equalsIgnoreCase("false"))
665: || (valueTrim.equalsIgnoreCase("off"))
666: || (valueTrim.equalsIgnoreCase("0"))
667: || (valueTrim.equalsIgnoreCase("no"))) {
668: return false;
669: } else {
670: throw new DataConversionException(name, "boolean");
671: }
672: }
673:
674: // Support a custom Namespace serialization so no two namespace
675: // object instances may exist for the same prefix/uri pair
676: private void writeObject(ObjectOutputStream out) throws IOException {
677:
678: out.defaultWriteObject();
679:
680: // We use writeObject() and not writeUTF() to minimize space
681: // This allows for writing pointers to already written strings
682: out.writeObject(namespace.getPrefix());
683: out.writeObject(namespace.getURI());
684: }
685:
686: private void readObject(ObjectInputStream in) throws IOException,
687: ClassNotFoundException {
688:
689: in.defaultReadObject();
690:
691: namespace = Namespace.getNamespace((String) in.readObject(),
692: (String) in.readObject());
693: }
694: }
|