KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > xquark > schema > datatypes > URI


1 /*
2  * This file belongs to the XQuark distribution.
3  * Copyright (C) 2003 Universite de Versailles Saint-Quentin.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307.
18  * You can also get it at http://www.gnu.org/licenses/lgpl.html
19  *
20  * For more information on this software, see http://www.xquark.org.
21  */

22
23 package org.xquark.schema.datatypes;
24
25 import java.io.Serializable JavaDoc;
26
27 /**********************************************************************
28 * This class represents an absolute or relative URI, as specified in
29 * IETF RFC 2396.
30 **********************************************************************/

31
32 public class URI implements Serializable JavaDoc {
33     private static final String JavaDoc RCSRevision = "$Revision: 1.1 $";
34     private static final String JavaDoc RCSName = "$Name: $";
35
36     /*******************************************************************
37     * MalformedURIExceptions are checked exceptions thrown when the
38     * URI string does not conform to the specification
39     *
40     ********************************************************************/

41     public static class MalformedURIException extends Exception JavaDoc {
42         private static final String JavaDoc RCSRevision = "$Revision: 1.1 $";
43         private static final String JavaDoc RCSName = "$Name: $";
44
45         /******************************************************************
46          * Constructs a MalformedURIException with no specified message.
47          ******************************************************************/

48         public MalformedURIException() {
49             super();
50         }
51
52         /*****************************************************************
53         * Constructs a MalformedURIException with the specified message.
54         *
55         * @param msg the message.
56         ******************************************************************/

57         public MalformedURIException(String JavaDoc msg) {
58             super(msg);
59         }
60     }
61
62     /** reserved characters */
63     private static final String JavaDoc RESERVED_CHARACTERS = ";/?:@&=+$,";
64
65     /** URI punctuation mark characters. Unreserved characters are
66      * the alphanumerics and these characters */

67     private static final String JavaDoc MARK_CHARACTERS = "-_.!~*'()";
68
69     /** scheme must start with an alphabetic and can be composed of
70      * alphanumerics and these characters */

71     private static final String JavaDoc SCHEME_CHARACTERS = "+-.";
72
73     private String JavaDoc scheme = null;
74     private String JavaDoc authority = null;
75     private String JavaDoc path = "";
76     private String JavaDoc query = null;
77     private String JavaDoc fragment = null;
78
79     /**
80      * Construct a new URI from a URI string. Three types of URI are recognized:
81      * 1. Absolute URIs with hierarchical part (generic URIs): those URIs will
82      * be decomposed into all possible components (scheme, authority, path, query).
83      * 2. Absolute URIs with opaque part: only the scheme and path (holding the
84      * opaque part) components are defined.
85      * 3. Relative URIs, which feature only a subset of the generic URIs components
86      *
87      * In addition, a fragment component can appear after all types of URI.
88      *
89      * @param uri the URI specification string
90      * @exception MalformedURIException if uri is not a valid URI specification
91      */

92     public URI(String JavaDoc uri) throws MalformedURIException {
93         if (uri == null)
94             throw new MalformedURIException("uri parameter cannot be empty.");
95         uri = uri.trim();
96         if (uri.length() == 0)
97             throw new MalformedURIException("uri parameter cannot be empty.");
98         parseURI(uri);
99     }
100
101     /**
102      * Construct a new URI from a base URI and a URI string.
103      * The URI string may be a relative URI.
104      *
105      * @param base the base URI
106      * @param uri the URI specification string
107      * @exception MalformedURIException if uri is not a valid URI specification
108      */

109     public URI(URI base, String JavaDoc uri) throws MalformedURIException {
110         if (uri == null || uri.trim().length() == 0) {
111             if (base != null) {
112                 copy(base);
113                 return;
114             } else {
115                 throw new MalformedURIException("base and uri parameters cannot be both empty.");
116             }
117         }
118         parseURI(uri);
119         if (base != null)
120             resolve(base);
121     }
122
123     /**
124      * Copy constructor
125      *
126      * @param orig the URI to copy
127      */

128     public URI(URI orig) {
129         copy(orig);
130     }
131
132     /**
133      * Copy all fields of another URI into this URI.
134      *
135      * @param orig the URI to copy
136      */

137     private void copy(URI orig) {
138         scheme = orig.getScheme();
139         authority = orig.getAuthority();
140         path = orig.getPath();
141         query = orig.getQuery();
142         fragment = orig.getFragment();
143     }
144
145     /**
146      * Initializes this URI from a base URI and a URI string.
147      *
148      * @param uri the URI string which may be an absolute or
149      * relative URI
150      *
151      * @exception MalformedURIException if uri is not a valid URI specification
152      */

153     private void parseURI(String JavaDoc uri) throws MalformedURIException {
154         int len = uri.length();
155         int index = 0;
156         int colonIndex = uri.indexOf(':', index);
157         int slashIndex = uri.indexOf('/', index);
158         if (colonIndex != -1 && (slashIndex == -1 || colonIndex < slashIndex)) {
159             // absolute URI
160
// get the scheme
161
index = parseScheme(uri);
162             if (index < len && uri.charAt(index) != '/') {
163                 // opaque URI: get the path component
164
index = parseOpaque(uri, index);
165             } else {
166                 // hierarchical URI
167
if (index + 1 < len && uri.charAt(index + 1) == '/') {
168                     // authority: get the authority component
169
index = parseAuthority(uri, index + 2);
170                 }
171                 // path: get the path component
172
index = parseAbsolutePath(uri, index);
173                 // get the query component of the generic URI
174
index = parseQuery(uri, index);
175             }
176         } else {
177             // relative URI (must be hierarchical)
178
if (index < len && uri.charAt(index) != '/') {
179                 // relative segment: get the first path component
180
index = parseRelativeSegment(uri, index);
181             } else if (index + 1 < len && uri.charAt(index + 1) == '/') {
182                 // authority: get the authority component
183
index = parseAuthority(uri, index + 2);
184             }
185             // path: get the path component
186
index = parseAbsolutePath(uri, index);
187             // get the query component of the hierarchical URI
188
index = parseQuery(uri, index);
189         }
190         index = parseFragment(uri, index);
191     }
192
193     /**
194      * Initialize the scheme for this URI from a URI string spec.
195      *
196      * @param uri the URI specification
197      *
198      * @return the start index for the next part
199      *
200      * @exception MalformedURIException if URI does not have a conformant scheme
201      */

202     private int parseScheme(String JavaDoc uri) throws MalformedURIException {
203         int len = uri.length();
204         int index = 0;
205         if (index >= len)
206             throw new MalformedURIException("Scheme cannot be empty in: " + uri + ".");
207         if (!isAlpha(uri.charAt(0)))
208             throw new MalformedURIException("Illegal character in scheme part of " + uri + " at position 0.");
209         index++;
210         while (index < len) {
211             char schemeChar = uri.charAt(index);
212             if (":".indexOf(schemeChar) != -1)
213                 break;
214             if (!isAlphanum(schemeChar) && SCHEME_CHARACTERS.indexOf(schemeChar) == -1)
215                 throw new MalformedURIException(
216                     "Illegal character in scheme part of " + uri + " at position " + index + ".");
217             index++;
218         }
219         scheme = uri.substring(0, index).toLowerCase();
220         return index + 1;
221     }
222
223     /**
224      * Initialize the authority for this URI from a URI string spec.
225      * Subdivision of authority is not taken care of, as it is irrelevant for
226      * XML Schema
227      *
228      * @param uri the URI specification
229      * @param start the start index for this part
230      *
231      * @return the start index for the next part
232      *
233      * @exception MalformedURIException if uri is not a valid URI specification
234      */

235     private int parseAuthority(String JavaDoc uri, int start) throws MalformedURIException {
236         int len = uri.length();
237         if (start >= len)
238             return start;
239         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(len-start);
240         int result = processURIPart(uri, start, "/?#", buffer);
241         authority = buffer.toString();
242         return result;
243     }
244
245     /**
246      * Initialize the opaque part for this URI from a URI string spec.
247      * The opaque part is stored as the path component
248      *
249      * @param uri the URI specification
250      * @param start the start index for this part
251      *
252      * @return the start index for the next part
253      *
254      * @exception MalformedURIException if uri is not a valid URI specification
255      */

256     private int parseOpaque(String JavaDoc uri, int start) throws MalformedURIException {
257         int len = uri.length();
258         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(len-start);
259         int result = processURIPart(uri, start, "#", buffer);
260         path = buffer.toString();
261         return result;
262     }
263
264     /**
265      * Initialize the path for this URI from a URI string spec.
266      *
267      * @param uri the URI specification
268      * @param start the start index for this part
269      *
270      * @return the start index for the next part
271      *
272      * @exception MalformedURIException if uri is not a valid URI specification
273      */

274     private int parseAbsolutePath(String JavaDoc uri, int start) throws MalformedURIException {
275         int len = uri.length();
276         if (start >= len) {
277             return start;
278         }
279         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(len-start);
280         int result = processURIPart(uri, start, "?#", buffer);
281         path += buffer.toString();
282         return result;
283     }
284
285     /**
286      * Initialize the first path segment for this relative URI from a URI string spec.
287      *
288      * @param uri the URI specification
289      * @param start the start index for this part
290      *
291      * @return the start index for the next part
292      *
293      * @exception MalformedURIException if uri is not a valid URI specification
294      */

295     private int parseRelativeSegment(String JavaDoc uri, int start) throws MalformedURIException {
296         int len = uri.length();
297         if (start >= len)
298             throw new MalformedURIException("Relative path cannot be empty in: " + uri + ".");
299         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(len-start);
300         int result = processURIPart(uri, start, "/?#", buffer);
301         path = buffer.toString();
302         return result;
303     }
304
305     /**
306      * Initialize the query part for this URI from a URI string spec.
307      *
308      * @param uri the URI specification
309      * @param start the start index for this part
310      *
311      * @return the start index for the next part
312      *
313      * @exception MalformedURIException if uri is not a valid URI specification
314      */

315     private int parseQuery(String JavaDoc uri, int start) throws MalformedURIException {
316         int len = uri.length();
317         if (start >= len)
318             return start;
319         if (uri.charAt(start) != '?')
320             return start;
321         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(len-start-1);
322         int result = processURIPart(uri, start+1, "#", buffer);
323         query = buffer.toString();
324         return result;
325     }
326
327     /**
328      * Initialize the fragment part for this URI from a URI string spec.
329      *
330      * @param uri the URI specification
331      * @param start the start index for this part
332      *
333      * @return the start index for the next part
334      *
335      * @exception MalformedURIException if uri is not a valid URI specification
336      */

337     private int parseFragment(String JavaDoc uri, int start) throws MalformedURIException {
338         int len = uri.length();
339         if (start >= len)
340             return start;
341         if (uri.charAt(start) != '#')
342             return start;
343         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(len-start-1);
344         int result = processURIPart(uri, start+1, "", buffer);
345         query = buffer.toString();
346         return result;
347     }
348     
349     /**
350      * Fill the result StringBuffer by processing the specified URI part.
351      * @param uri the URI specification string
352      * @param start the start index in the specification string
353      * @param stopChars the set of characters which denote the end of the processed part
354      * @param result the buffer containing the part characters, after unescaping
355      * @return the start index for the next part
356      */

357     private int processURIPart(String JavaDoc uri, int start, String JavaDoc stopChars, StringBuffer JavaDoc result) throws MalformedURIException {
358         int len = uri.length();
359         int index = start;
360         if (index >= len)
361             return index;
362         while (index < len) {
363             char c = uri.charAt(index);
364             if (stopChars.indexOf(c) != -1)
365                 break;
366             if (!isUnreservedCharacter(c) && !isReservedCharacter(c)) {
367                 if (c == '%' && index + 2 < len && isHex(uri.charAt(index + 1)) && isHex(uri.charAt(index + 2))) {
368                     result.append((char)Integer.parseInt(uri.substring(index+1, index+3), 16));
369                     index += 3;
370                 } else {
371                     throw new MalformedURIException("Illegal character in " + uri + " at position " + index + ".");
372                 }
373             } else {
374                 result.append(c);
375                 index++;
376             }
377         }
378         return index;
379     }
380
381     /**
382      * Append the provided URI part into the result buffer, escaping the characters when required
383      * @param part the URI part
384      * @param result the buffer containing the escaped characters
385      */

386     private void escapePart(String JavaDoc part, StringBuffer JavaDoc result) {
387         for (int i = 0; i < part.length(); i++) {
388             char c = part.charAt(i);
389             if (!isUnreservedCharacter(c) && !isReservedCharacter(c)) {
390                 result.append('%');
391                 result.append(Integer.toHexString((int)c));
392             } else {
393                 result.append(c);
394             }
395         }
396     }
397     
398     /**
399      * Transform this relative URI into an absolute URI,
400      * following the RFC 2396 spec.
401      * @param base the base URI to resolve to
402      */

403     private void resolve(URI base) throws MalformedURIException {
404         if (!base.isHierarchical())
405             throw new MalformedURIException("Cannot resolve a relative URI to a non-hierarchical base");
406         if (base.isRelative())
407             throw new MalformedURIException("Cannot resolve a relative URI to a relative base");
408         if (scheme != null)
409             return;
410         setScheme(base.getScheme());
411         if (authority != null)
412             return;
413         setAuthority(base.getAuthority());
414         if (path.length() == 0) {
415             setPath(base.getPath());
416         } else if (path.charAt(0) == '/') {
417             return;
418         } else {
419             StringBuffer JavaDoc absPath = new StringBuffer JavaDoc(base.getPath());
420             int lastSlash = base.getPath().lastIndexOf('/');
421             if (lastSlash != -1) {
422                 absPath.setLength(lastSlash + 1);
423             }
424             absPath.append(path);
425             String JavaDoc rawPath = absPath.toString();
426             int index = -1;
427             int delta = 0;
428             while ((index = rawPath.indexOf("/./", index + 1)) != -1) {
429                 absPath.delete(index + 1 - delta, index + 3 - delta);
430                 delta += 2;
431             }
432             if (rawPath.endsWith("/.")) {
433                 absPath.setLength(absPath.length() - 1);
434                 delta += 1;
435             }
436             if (delta > 0) {
437                 rawPath = absPath.toString();
438             }
439             while ((index = rawPath.indexOf("/../")) > 0) {
440                 int prevIndex = rawPath.lastIndexOf('/', index - 1);
441                 if (prevIndex != -1) {
442                     rawPath = rawPath.substring(0, prevIndex+1).concat(rawPath.substring(index+4));
443                }
444             }
445             if (rawPath.endsWith("/..")) {
446                 int prevIndex = rawPath.lastIndexOf('/', rawPath.length() - 4);
447                 if (prevIndex != -1 && !"..".equals(rawPath.substring(prevIndex + 1, prevIndex + 3))) {
448                     rawPath = rawPath.substring(0, prevIndex+1);
449                 }
450             }
451             path = rawPath;
452         }
453     }
454
455     /**
456      * Get the scheme for this URI.
457      *
458      * @return the scheme for this URI
459      */

460     public String JavaDoc getScheme() {
461         return scheme;
462     }
463
464     /**
465     * Get the authority for this URI.
466     *
467     * @return the authority for this URI (null if not specified).
468     */

469     public String JavaDoc getAuthority() {
470         return authority;
471     }
472
473     /**
474      * Get the path for this URI. Note that the value returned is the path
475      * only and does not include the query string or fragment.
476      *
477      * @return the path for this URI.
478      */

479     public String JavaDoc getPath() {
480         return path;
481     }
482
483     /**
484      * Get the query string for this URI.
485      *
486      * @return the query string for this URI. Null is returned if there
487      * was no "?" in the URI spec, empty string if there was a
488      * "?" but no query string following it.
489      */

490     public String JavaDoc getQuery() {
491         return query;
492     }
493
494     /**
495      * Get the fragment for this URI.
496      *
497      * @return the fragment for this URI. Null is returned if there
498      * was no "#" in the URI spec, empty string if there was a
499      * "#" but no fragment following it.
500      */

501     public String JavaDoc getFragment() {
502         return fragment;
503     }
504
505     /**
506      * Set the scheme for this URI.
507      *
508      * @param scheme the new scheme value
509      */

510     private void setScheme(String JavaDoc scheme) {
511         this.scheme = scheme;
512     }
513
514     /**
515     * Set the authority for this URI.
516     *
517     * @param auth the new authority value
518     */

519     private void setAuthority(String JavaDoc auth) {
520         this.authority = auth;
521     }
522
523     /**
524      * Set the path for this URI.
525      *
526      * @param path the new path value
527      */

528     private void setPath(String JavaDoc path) {
529         this.path = path;
530     }
531
532     /**
533      * Set the query part for this URI.
534      *
535      * @param query the new query value
536      *
537      */

538     public void setQuery(String JavaDoc query) {
539         this.query = query;
540     }
541
542     /**
543      * Set the fragment for this URI.
544      *
545      * @param fragment the new fragment value
546      *
547      */

548     public void setFragment(String JavaDoc fragment) throws MalformedURIException {
549         this.fragment = fragment;
550     }
551
552     /**
553      * Compare an object to this URI.
554      *
555      * @param obj the Object to test for equality.
556      *
557      * @return true if obj is a URI with all values equal to this
558      * URI, false otherwise
559      */

560     public boolean equals(Object JavaDoc obj) {
561         if (this == obj) return true;
562         if (obj instanceof URI) {
563             URI testURI = (URI) obj;
564             if (((scheme == null && testURI.scheme == null)
565                 || (scheme != null && testURI.scheme != null && scheme.equals(testURI.scheme)))
566                 && ((authority == null && testURI.authority == null)
567                     || (authority != null && testURI.authority != null && authority.equals(testURI.authority)))
568                 && ((path == null && testURI.path == null)
569                     || (path != null && testURI.path != null && path.equals(testURI.path)))
570                 && ((query == null && testURI.query == null)
571                     || (query != null && testURI.query != null && query.equals(testURI.query)))
572                 && ((fragment == null && testURI.fragment == null)
573                     || (fragment != null && testURI.fragment != null && fragment.equals(testURI.fragment)))) {
574                 return true;
575             }
576         }
577         return false;
578     }
579
580     /**
581      * Computes the hashcode value for this URI, based on the hashcode of its components
582      *
583      * @return the hashcode value
584      */

585     public int hashCode() {
586         return (scheme == null ? 0 : scheme.hashCode())
587             ^ (authority == null ? 0 : authority.hashCode())
588             ^ (path == null ? 0 : path.hashCode())
589             ^ (query == null ? 0 : query.hashCode())
590             ^ (fragment == null ? 0 : fragment.hashCode());
591     }
592
593     /**
594      * Get the URI as a string specification. See RFC 2396 Section 5.2.
595      *
596      * @return the URI string specification
597      */

598     public String JavaDoc toString() {
599         StringBuffer JavaDoc uriSpecString = new StringBuffer JavaDoc();
600         if (scheme != null) {
601             uriSpecString.append(scheme);
602             uriSpecString.append(':');
603         }
604         if (authority != null) {
605             uriSpecString.append("//");
606             escapePart(authority, uriSpecString);
607         }
608         escapePart(path, uriSpecString);
609         if (query != null) {
610             uriSpecString.append('?');
611             escapePart(query, uriSpecString);
612         }
613         if (fragment != null) {
614             uriSpecString.append('#');
615             escapePart(fragment, uriSpecString);
616         }
617         return uriSpecString.toString();
618     }
619
620     /**
621      * Get the URI length.
622      *
623      * @return the URI length
624      */

625     public int getLength() {
626         int len = 0;
627         if (scheme != null) {
628             len += scheme.length()+1;
629         }
630         if (authority != null) {
631             len += authority.length()+2;
632         }
633         len += path.length();
634         if (query != null) {
635             len += query.length()+1;
636         }
637         if (fragment != null) {
638             len += fragment.length()+1;
639         }
640         return len;
641     }
642
643     /**
644      * Indicate whether this URI uses the generic hierarchical syntax
645      *
646      * @return true if this URI uses the hierarchical URI syntax
647      */

648     public boolean isHierarchical() {
649         return (scheme == null || path.charAt(0) == '/');
650     }
651
652     /**
653      * Indicate whether this URI is relative, i.e. has no specified scheme
654      *
655      * @return true if this URI is relative
656      */

657     public boolean isRelative() {
658         return (scheme == null);
659     }
660
661     /**
662      * Determine whether a char is a digit.
663      *
664      * @return true if the char is betweeen '0' and '9', false otherwise
665      */

666     private static boolean isDigit(char p_char) {
667         return p_char >= '0' && p_char <= '9';
668     }
669
670     /**
671      * Determine whether a character is a hexadecimal character.
672      *
673      * @return true if the char is betweeen '0' and '9', 'a' and 'f'
674      * or 'A' and 'F', false otherwise
675      */

676     private static boolean isHex(char p_char) {
677         return (isDigit(p_char) || (p_char >= 'a' && p_char <= 'f') || (p_char >= 'A' && p_char <= 'F'));
678     }
679
680     /**
681      * Determine whether a char is an alphabetic character: a-z or A-Z
682      *
683      * @return true if the char is alphabetic, false otherwise
684      */

685     private static boolean isAlpha(char p_char) {
686         return ((p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z'));
687     }
688
689     /**
690      * Determine whether a char is an alphanumeric: 0-9, a-z or A-Z
691      *
692      * @return true if the char is alphanumeric, false otherwise
693      */

694     private static boolean isAlphanum(char p_char) {
695         return (isAlpha(p_char) || isDigit(p_char));
696     }
697
698     /**
699      * Determine whether a character is a reserved character:
700      * ';', '/', '?', ':', '@', '&', '=', '+', '$' or ','
701      *
702      * @return true if the string contains any reserved characters
703      */

704     private static boolean isReservedCharacter(char p_char) {
705         return RESERVED_CHARACTERS.indexOf(p_char) != -1;
706     }
707
708     /**
709      * Determine whether a char is an unreserved character.
710      *
711      * @return true if the char is unreserved, false otherwise
712      */

713     private static boolean isUnreservedCharacter(char p_char) {
714         return (isAlphanum(p_char) || MARK_CHARACTERS.indexOf(p_char) != -1);
715     }
716
717 }
718
Popular Tags