001    /*
002     * The Apache Software License, Version 1.1
003     *
004     *
005     * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
006     * 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 following disclaimer in
017     *    the documentation and/or other materials provided with the
018     *    distribution.
019     *
020     * 3. The end-user documentation included with the redistribution,
021     *    if any, must include the following acknowledgment:
022     *       "This product includes software developed by the
023     *        Apache Software Foundation ( http://www.apache.org/ )."
024     *    Alternately, this acknowledgment may appear in the software itself,
025     *    if and wherever such third-party acknowledgments normally appear.
026     *
027     * 4. The names "Axis" and "Apache Software Foundation" must
028     *    not be used to endorse or promote products derived from this
029     *    software without prior written permission. For written
030     *    permission, please contact apache@apache.org .
031     *
032     * 5. Products derived from this software may not be called "Apache",
033     *    nor may "Apache" appear in their name, without prior written
034     *    permission of the Apache Software Foundation.
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 APACHE SOFTWARE FOUNDATION OR
040     * ITS 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     *
050     * This software consists of voluntary contributions made by many
051     * individuals on behalf of the Apache Software Foundation.  For more
052     * information on the Apache Software Foundation, please see
053     * < http://www.apache.org/ >.
054     */
055    package groovy.xml;
056    
057    import java.io.IOException;
058    import java.io.ObjectInputStream;
059    import java.io.Serializable;
060    
061    /**
062     * <code>QName</code> class represents the value of a qualified name
063     * as specified in <a href=" http://www.w3.org/TR/xmlschema-2/#QName ">XML
064     * Schema Part2: Datatypes specification</a>.
065     * <p>
066     * The value of a QName contains a <b>namespaceURI</b>, a <b>localPart</b> and a <b>prefix</b>.
067     * The localPart provides the local part of the qualified name. The
068     * namespaceURI is a URI reference identifying the namespace.
069     *
070     * @version 1.1
071     */
072    public class QName implements Serializable {
073    
074        /** comment/shared empty string */
075        private static final String emptyString = "".intern();
076    
077        /** Field namespaceURI */
078        private String namespaceURI;
079    
080        /** Field localPart */
081        private String localPart;
082    
083        /** Field prefix */
084        private String prefix;
085    
086        /**
087         * Constructor for the QName.
088         *
089         * @param localPart Local part of the QName
090         */
091        public QName(String localPart) {
092            this(emptyString, localPart, emptyString);
093        }
094    
095        /**
096         * Constructor for the QName.
097         *
098         * @param namespaceURI Namespace URI for the QName
099         * @param localPart Local part of the QName.
100         */
101        public QName(String namespaceURI, String localPart) {
102            this(namespaceURI, localPart, emptyString);
103        }
104    
105        /**
106         * Constructor for the QName.
107         *
108         * @param namespaceURI Namespace URI for the QName
109         * @param localPart Local part of the QName.
110         * @param prefix Prefix of the QName.
111         */
112        public QName(String namespaceURI, String localPart, String prefix) {
113            this.namespaceURI = (namespaceURI == null)
114                    ? emptyString
115                    : namespaceURI.intern();
116            if (localPart == null) {
117                throw new IllegalArgumentException("invalid QName local part");
118            } else {
119                this.localPart = localPart.intern();
120            }
121    
122            if (prefix == null) {
123                throw new IllegalArgumentException("invalid QName prefix");
124            } else {
125                this.prefix = prefix.intern();
126            }
127        }
128    
129        /**
130         * Gets the Namespace URI for this QName
131         *
132         * @return Namespace URI
133         */
134        public String getNamespaceURI() {
135            return namespaceURI;
136        }
137    
138        /**
139         * Gets the Local part for this QName
140         *
141         * @return Local part
142         */
143        public String getLocalPart() {
144            return localPart;
145        }
146    
147        /**
148         * Gets the Prefix for this QName
149         *
150         * @return Prefix
151         */
152        public String getPrefix() {
153            return prefix;
154        }
155    
156        /**
157         * Returns the fully qualified name of this QName
158         *
159         * @return  a string representation of the QName
160         */
161        public String getQualifiedName() {
162    
163            return ((prefix.equals(emptyString))
164                    ? localPart
165                    : prefix + ':' + localPart);
166        }
167    
168        /**
169         * Returns a string representation of this QName
170         *
171         * @return  a string representation of the QName
172         */
173        public String toString() {
174    
175            return ((namespaceURI.equals(emptyString))
176                    ? localPart
177                    : '{' + namespaceURI + '}' + localPart);
178        }
179    
180        /**
181         * Tests this QName for equality with another object.
182         * <p>
183         * If the given object is not a QName or String equivalent or is null then this method
184         * returns <tt>false</tt>.
185         * <p>
186         * For two QNames to be considered equal requires that both
187         * localPart and namespaceURI must be equal. This method uses
188         * <code>String.equals</code> to check equality of localPart
189         * and namespaceURI. Any class that extends QName is required
190         * to satisfy this equality contract.
191         *
192         * If the supplied object is a String, then it is split in two on the last colon
193         * and the first half is compared against the prefix || namespaceURI
194         * and the second half is compared against the localPart
195         *
196         * i.e. assert new QName("namespace","localPart").equals("namespace:localPart")
197         *
198         * Intended Usage: for gpath accessors, e.g. root.'urn:mynamespace:node'
199         *
200         * Warning: this equivalence is not commutative,
201         * i.e. qname.equals(string) may be true/false  but string.equals(qname) is always false
202         *
203         * <p>
204         * This method satisfies the general contract of the <code>Object.equals</code> method.
205         *
206         * @param o the reference object with which to compare
207         *
208         * @return <code>true</code> if the given object is identical to this
209         *      QName: <code>false</code> otherwise.
210         */
211        public boolean equals(Object o) {
212            if (this == o) return true;
213            if (o == null) return false;
214            if (o instanceof QName) {
215                final QName qName = (QName) o;
216                if (!namespaceURI.equals(qName.namespaceURI)) return false;
217                return localPart.equals(qName.localPart);
218    
219            } else if (o instanceof String) {
220                final String string = (String)o;
221                if (string.length() == 0) return false;
222                int lastColonIndex = string.lastIndexOf(":");
223                if (lastColonIndex < 0 || lastColonIndex == string.length() - 1) return false;
224                final String stringPrefix = string.substring(0,lastColonIndex);
225                final String stringLocalPart = string.substring(lastColonIndex + 1);
226                if (stringPrefix.equals(prefix) || stringPrefix.equals(namespaceURI)) {
227                    return localPart.equals(stringLocalPart);
228                }
229                return false;
230            }
231            return false;
232        }
233    
234        /**
235         * Returns a QName holding the value of the specified String.
236         * <p>
237         * The string must be in the form returned by the QName.toString()
238         * method, i.e. "{namespaceURI}localPart", with the "{namespaceURI}"
239         * part being optional.
240         * <p>
241         * This method doesn't do a full validation of the resulting QName.
242         * In particular, it doesn't check that the resulting namespace URI
243         * is a legal URI (per RFC 2396 and RFC 2732), nor that the resulting
244         * local part is a legal NCName per the XML Namespaces specification.
245         *
246         * @param s the string to be parsed
247         * @throws java.lang.IllegalArgumentException If the specified String cannot be parsed as a QName
248         * @return QName corresponding to the given String
249         */
250        public static QName valueOf(String s) {
251    
252            if ((s == null) || s.equals("")) {
253                throw new IllegalArgumentException("invalid QName literal");
254            }
255    
256            if (s.charAt(0) == '{') {
257                int i = s.indexOf('}');
258    
259                if (i == -1) {
260                    throw new IllegalArgumentException("invalid QName literal");
261                }
262    
263                if (i == s.length() - 1) {
264                    throw new IllegalArgumentException("invalid QName literal");
265                } else {
266                    return new QName(s.substring(1, i), s.substring(i + 1));
267                }
268            } else {
269                return new QName(s);
270            }
271        }
272    
273        /**
274         * Returns a hash code value for this QName object. The hash code
275         * is based on both the localPart and namespaceURI parts of the
276         * QName. This method satisfies the  general contract of the
277         * <code>Object.hashCode</code> method.
278         *
279         * @return a hash code value for this Qname object
280         */
281        public int hashCode() {
282            int result;
283            result = namespaceURI.hashCode();
284            result = 29 * result + localPart.hashCode();
285            return result;
286        }
287    
288        /**
289         * Ensure that deserialization properly interns the results.
290         * @param in the ObjectInputStream to be read
291         */
292        private void readObject(ObjectInputStream in) throws
293                IOException, ClassNotFoundException {
294            in.defaultReadObject();
295    
296            namespaceURI = namespaceURI.intern();
297            localPart = localPart.intern();
298            prefix = prefix.intern();
299        }
300    }