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 }