KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > util > URL


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18
19 package org.apache.catalina.util;
20
21
22 import java.io.Serializable JavaDoc;
23 import java.net.MalformedURLException JavaDoc;
24
25
26 /**
27  * <p><strong>URL</strong> is designed to provide public APIs for parsing
28  * and synthesizing Uniform Resource Locators as similar as possible to the
29  * APIs of <code>java.net.URL</code>, but without the ability to open a
30  * stream or connection. One of the consequences of this is that you can
31  * construct URLs for protocols for which a URLStreamHandler is not
32  * available (such as an "https" URL when JSSE is not installed).</p>
33  *
34  * <p><strong>WARNING</strong> - This class assumes that the string
35  * representation of a URL conforms to the <code>spec</code> argument
36  * as described in RFC 2396 "Uniform Resource Identifiers: Generic Syntax":
37  * <pre>
38  * &lt;scheme&gt;//&lt;authority&gt;&lt;path&gt;?&lt;query&gt;#&lt;fragment&gt;
39  * </pre></p>
40  *
41  * <p><strong>FIXME</strong> - This class really ought to end up in a Commons
42  * package someplace.</p>
43  *
44  * @author Craig R. McClanahan
45  * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
46  */

47
48 public final class URL implements Serializable JavaDoc {
49
50
51     // ----------------------------------------------------------- Constructors
52

53
54     /**
55      * Create a URL object from the specified String representation.
56      *
57      * @param spec String representation of the URL
58      *
59      * @exception MalformedURLException if the string representation
60      * cannot be parsed successfully
61      */

62     public URL(String JavaDoc spec) throws MalformedURLException JavaDoc {
63
64         this(null, spec);
65
66     }
67
68
69     /**
70      * Create a URL object by parsing a string representation relative
71      * to a specified context. Based on logic from JDK 1.3.1's
72      * <code>java.net.URL</code>.
73      *
74      * @param context URL against which the relative representation
75      * is resolved
76      * @param spec String representation of the URL (usually relative)
77      *
78      * @exception MalformedURLException if the string representation
79      * cannot be parsed successfully
80      */

81     public URL(URL context, String JavaDoc spec) throws MalformedURLException JavaDoc {
82
83         String JavaDoc original = spec;
84         int i, limit, c;
85         int start = 0;
86         String JavaDoc newProtocol = null;
87         boolean aRef = false;
88
89         try {
90
91             // Eliminate leading and trailing whitespace
92
limit = spec.length();
93             while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
94                 limit--;
95             }
96             while ((start < limit) && (spec.charAt(start) <= ' ')) {
97                 start++;
98             }
99
100             // If the string representation starts with "url:", skip it
101
if (spec.regionMatches(true, start, "url:", 0, 4)) {
102                 start += 4;
103             }
104
105             // Is this a ref relative to the context URL?
106
if ((start < spec.length()) && (spec.charAt(start) == '#')) {
107                 aRef = true;
108             }
109
110             // Parse out the new protocol
111
for (i = start; !aRef && (i < limit) &&
112                      ((c = spec.charAt(i)) != '/'); i++) {
113                 if (c == ':') {
114                     String JavaDoc s = spec.substring(start, i).toLowerCase();
115                     // Assume all protocols are valid
116
newProtocol = s;
117                     start = i + 1;
118                     break;
119                 }
120             }
121
122             // Only use our context if the protocols match
123
protocol = newProtocol;
124             if ((context != null) && ((newProtocol == null) ||
125                  newProtocol.equalsIgnoreCase(context.getProtocol()))) {
126                 // If the context is a hierarchical URL scheme and the spec
127
// contains a matching scheme then maintain backwards
128
// compatibility and treat it as if the spec didn't contain
129
// the scheme; see 5.2.3 of RFC2396
130
if ((context.getPath() != null) &&
131                     (context.getPath().startsWith("/")))
132                     newProtocol = null;
133                 if (newProtocol == null) {
134                     protocol = context.getProtocol();
135                     authority = context.getAuthority();
136                     userInfo = context.getUserInfo();
137                     host = context.getHost();
138                     port = context.getPort();
139                     file = context.getFile();
140                     int question = file.lastIndexOf("?");
141                     if (question < 0)
142                         path = file;
143                     else
144                         path = file.substring(0, question);
145                 }
146             }
147
148             if (protocol == null)
149                 throw new MalformedURLException JavaDoc("no protocol: " + original);
150
151             // Parse out any ref portion of the spec
152
i = spec.indexOf('#', start);
153             if (i >= 0) {
154                 ref = spec.substring(i + 1, limit);
155                 limit = i;
156             }
157
158             // Parse the remainder of the spec in a protocol-specific fashion
159
parse(spec, start, limit);
160             if (context != null)
161                 normalize();
162
163
164         } catch (MalformedURLException JavaDoc e) {
165             throw e;
166         } catch (Exception JavaDoc e) {
167             throw new MalformedURLException JavaDoc(e.toString());
168         }
169
170     }
171
172
173
174
175
176     /**
177      * Create a URL object from the specified components. The default port
178      * number for the specified protocol will be used.
179      *
180      * @param protocol Name of the protocol to use
181      * @param host Name of the host addressed by this protocol
182      * @param file Filename on the specified host
183      *
184      * @exception MalformedURLException is never thrown, but present for
185      * compatible APIs
186      */

187     public URL(String JavaDoc protocol, String JavaDoc host, String JavaDoc file)
188         throws MalformedURLException JavaDoc {
189
190         this(protocol, host, -1, file);
191
192     }
193
194
195     /**
196      * Create a URL object from the specified components. Specifying a port
197      * number of -1 indicates that the URL should use the default port for
198      * that protocol. Based on logic from JDK 1.3.1's
199      * <code>java.net.URL</code>.
200      *
201      * @param protocol Name of the protocol to use
202      * @param host Name of the host addressed by this protocol
203      * @param port Port number, or -1 for the default port for this protocol
204      * @param file Filename on the specified host
205      *
206      * @exception MalformedURLException is never thrown, but present for
207      * compatible APIs
208      */

209     public URL(String JavaDoc protocol, String JavaDoc host, int port, String JavaDoc file)
210         throws MalformedURLException JavaDoc {
211
212         this.protocol = protocol;
213         this.host = host;
214         this.port = port;
215
216         int hash = file.indexOf('#');
217         this.file = hash < 0 ? file : file.substring(0, hash);
218         this.ref = hash < 0 ? null : file.substring(hash + 1);
219         int question = file.lastIndexOf('?');
220         if (question >= 0) {
221             query = file.substring(question + 1);
222             path = file.substring(0, question);
223         } else
224             path = file;
225
226         if ((host != null) && (host.length() > 0))
227             authority = (port == -1) ? host : host + ":" + port;
228
229     }
230
231
232     // ----------------------------------------------------- Instance Variables
233

234
235     /**
236      * The authority part of the URL.
237      */

238     private String JavaDoc authority = null;
239
240
241     /**
242      * The filename part of the URL.
243      */

244     private String JavaDoc file = null;
245
246
247     /**
248      * The host name part of the URL.
249      */

250     private String JavaDoc host = null;
251
252
253     /**
254      * The path part of the URL.
255      */

256     private String JavaDoc path = null;
257
258
259     /**
260      * The port number part of the URL.
261      */

262     private int port = -1;
263
264
265     /**
266      * The protocol name part of the URL.
267      */

268     private String JavaDoc protocol = null;
269
270
271     /**
272      * The query part of the URL.
273      */

274     private String JavaDoc query = null;
275
276
277     /**
278      * The reference part of the URL.
279      */

280     private String JavaDoc ref = null;
281
282
283     /**
284      * The user info part of the URL.
285      */

286     private String JavaDoc userInfo = null;
287
288
289     // --------------------------------------------------------- Public Methods
290

291
292     /**
293      * Compare two URLs for equality. The result is <code>true</code> if and
294      * only if the argument is not null, and is a <code>URL</code> object
295      * that represents the same <code>URL</code> as this object. Two
296      * <code>URLs</code> are equal if they have the same protocol and
297      * reference the same host, the same port number on the host,
298      * and the same file and anchor on the host.
299      *
300      * @param obj The URL to compare against
301      */

302     public boolean equals(Object JavaDoc obj) {
303
304         if (obj == null)
305             return (false);
306         if (!(obj instanceof URL))
307             return (false);
308         URL other = (URL) obj;
309         if (!sameFile(other))
310             return (false);
311         return (compare(ref, other.getRef()));
312
313     }
314
315
316     /**
317      * Return the authority part of the URL.
318      */

319     public String JavaDoc getAuthority() {
320
321         return (this.authority);
322
323     }
324
325
326     /**
327      * Return the filename part of the URL. <strong>NOTE</strong> - For
328      * compatibility with <code>java.net.URL</code>, this value includes
329      * the query string if there was one. For just the path portion,
330      * call <code>getPath()</code> instead.
331      */

332     public String JavaDoc getFile() {
333
334         if (file == null)
335             return ("");
336         return (this.file);
337
338     }
339
340
341     /**
342      * Return the host name part of the URL.
343      */

344     public String JavaDoc getHost() {
345
346         return (this.host);
347
348     }
349
350
351     /**
352      * Return the path part of the URL.
353      */

354     public String JavaDoc getPath() {
355
356         if (this.path == null)
357             return ("");
358         return (this.path);
359
360     }
361
362
363     /**
364      * Return the port number part of the URL.
365      */

366     public int getPort() {
367
368         return (this.port);
369
370     }
371
372
373     /**
374      * Return the protocol name part of the URL.
375      */

376     public String JavaDoc getProtocol() {
377
378         return (this.protocol);
379
380     }
381
382
383     /**
384      * Return the query part of the URL.
385      */

386     public String JavaDoc getQuery() {
387
388         return (this.query);
389
390     }
391
392
393     /**
394      * Return the reference part of the URL.
395      */

396     public String JavaDoc getRef() {
397
398         return (this.ref);
399
400     }
401
402
403     /**
404      * Return the user info part of the URL.
405      */

406     public String JavaDoc getUserInfo() {
407
408         return (this.userInfo);
409
410     }
411
412
413     /**
414      * Normalize the <code>path</code> (and therefore <code>file</code>)
415      * portions of this URL.
416      * <p>
417      * <strong>NOTE</strong> - This method is not part of the public API
418      * of <code>java.net.URL</code>, but is provided as a value added
419      * service of this implementation.
420      *
421      * @exception MalformedURLException if a normalization error occurs,
422      * such as trying to move about the hierarchical root
423      */

424     public void normalize() throws MalformedURLException JavaDoc {
425
426         // Special case for null path
427
if (path == null) {
428             if (query != null)
429                 file = "?" + query;
430             else
431                 file = "";
432             return;
433         }
434
435         // Create a place for the normalized path
436
String JavaDoc normalized = path;
437         if (normalized.equals("/.")) {
438             path = "/";
439             if (query != null)
440                 file = path + "?" + query;
441             else
442                 file = path;
443             return;
444         }
445
446         // Normalize the slashes and add leading slash if necessary
447
if (normalized.indexOf('\\') >= 0)
448             normalized = normalized.replace('\\', '/');
449         if (!normalized.startsWith("/"))
450             normalized = "/" + normalized;
451
452         // Resolve occurrences of "//" in the normalized path
453
while (true) {
454             int index = normalized.indexOf("//");
455             if (index < 0)
456                 break;
457             normalized = normalized.substring(0, index) +
458                 normalized.substring(index + 1);
459         }
460
461         // Resolve occurrences of "/./" in the normalized path
462
while (true) {
463             int index = normalized.indexOf("/./");
464             if (index < 0)
465                 break;
466             normalized = normalized.substring(0, index) +
467                 normalized.substring(index + 2);
468         }
469
470         // Resolve occurrences of "/../" in the normalized path
471
while (true) {
472             int index = normalized.indexOf("/../");
473             if (index < 0)
474                 break;
475             if (index == 0)
476                 throw new MalformedURLException JavaDoc
477                     ("Invalid relative URL reference");
478             int index2 = normalized.lastIndexOf('/', index - 1);
479             normalized = normalized.substring(0, index2) +
480                 normalized.substring(index + 3);
481         }
482
483         // Resolve occurrences of "/." at the end of the normalized path
484
if (normalized.endsWith("/."))
485             normalized = normalized.substring(0, normalized.length() - 1);
486
487         // Resolve occurrences of "/.." at the end of the normalized path
488
if (normalized.endsWith("/..")) {
489             int index = normalized.length() - 3;
490             int index2 = normalized.lastIndexOf('/', index - 1);
491             if (index2 < 0)
492                 throw new MalformedURLException JavaDoc
493                     ("Invalid relative URL reference");
494             normalized = normalized.substring(0, index2 + 1);
495         }
496
497         // Return the normalized path that we have completed
498
path = normalized;
499         if (query != null)
500             file = path + "?" + query;
501         else
502             file = path;
503
504     }
505
506
507     /**
508      * Compare two URLs, excluding the "ref" fields. Returns <code>true</code>
509      * if this <code>URL</code> and the <code>other</code> argument both refer
510      * to the same resource. The two <code>URLs</code> might not both contain
511      * the same anchor.
512      */

513     public boolean sameFile(URL other) {
514
515         if (!compare(protocol, other.getProtocol()))
516             return (false);
517         if (!compare(host, other.getHost()))
518             return (false);
519         if (port != other.getPort())
520             return (false);
521         if (!compare(file, other.getFile()))
522             return (false);
523         return (true);
524
525     }
526
527
528     /**
529      * Return a string representation of this URL. This follow the rules in
530      * RFC 2396, Section 5.2, Step 7.
531      */

532     public String JavaDoc toExternalForm() {
533
534         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
535         if (protocol != null) {
536             sb.append(protocol);
537             sb.append(":");
538         }
539         if (authority != null) {
540             sb.append("//");
541             sb.append(authority);
542         }
543         if (path != null)
544             sb.append(path);
545         if (query != null) {
546             sb.append('?');
547             sb.append(query);
548         }
549         if (ref != null) {
550             sb.append('#');
551             sb.append(ref);
552         }
553         return (sb.toString());
554
555     }
556
557
558     /**
559      * Return a string representation of this object.
560      */

561     public String JavaDoc toString() {
562
563         StringBuffer JavaDoc sb = new StringBuffer JavaDoc("URL[");
564         sb.append("authority=");
565         sb.append(authority);
566         sb.append(", file=");
567         sb.append(file);
568         sb.append(", host=");
569         sb.append(host);
570         sb.append(", port=");
571         sb.append(port);
572         sb.append(", protocol=");
573         sb.append(protocol);
574         sb.append(", query=");
575         sb.append(query);
576         sb.append(", ref=");
577         sb.append(ref);
578         sb.append(", userInfo=");
579         sb.append(userInfo);
580         sb.append("]");
581         return (sb.toString());
582
583         // return (toExternalForm());
584

585     }
586
587
588     // -------------------------------------------------------- Private Methods
589

590
591     /**
592      * Compare to String values for equality, taking appropriate care if one
593      * or both of the values are <code>null</code>.
594      *
595      * @param first First string
596      * @param second Second string
597      */

598     private boolean compare(String JavaDoc first, String JavaDoc second) {
599
600         if (first == null) {
601             if (second == null)
602                 return (true);
603             else
604                 return (false);
605         } else {
606             if (second == null)
607                 return (false);
608             else
609                 return (first.equals(second));
610         }
611
612     }
613
614
615     /**
616      * Parse the specified portion of the string representation of a URL,
617      * assuming that it has a format similar to that for <code>http</code>.
618      *
619      * <p><strong>FIXME</strong> - This algorithm can undoubtedly be optimized
620      * for performance. However, that needs to wait until after sufficient
621      * unit tests are implemented to guarantee correct behavior with no
622      * regressions.</p>
623      *
624      * @param spec String representation being parsed
625      * @param start Starting offset, which will be just after the ':' (if
626      * there is one) that determined the protocol name
627      * @param limit Ending position, which will be the position of the '#'
628      * (if there is one) that delimited the anchor
629      *
630      * @exception MalformedURLException if a parsing error occurs
631      */

632     private void parse(String JavaDoc spec, int start, int limit)
633         throws MalformedURLException JavaDoc {
634
635         // Trim the query string (if any) off the tail end
636
int question = spec.lastIndexOf('?', limit - 1);
637         if ((question >= 0) && (question < limit)) {
638             query = spec.substring(question + 1, limit);
639             limit = question;
640         } else {
641             query = null;
642         }
643
644         // Parse the authority section
645
if (spec.indexOf("//", start) == start) {
646             int pathStart = spec.indexOf("/", start + 2);
647             if ((pathStart >= 0) && (pathStart < limit)) {
648                 authority = spec.substring(start + 2, pathStart);
649                 start = pathStart;
650             } else {
651                 authority = spec.substring(start + 2, limit);
652                 start = limit;
653             }
654             if (authority.length() > 0) {
655                 int at = authority.indexOf('@');
656                 if( at >= 0 ) {
657                     userInfo = authority.substring(0,at);
658                 }
659                 int colon = authority.indexOf(':',at+1);
660                 if (colon >= 0) {
661                     try {
662                         port =
663                             Integer.parseInt(authority.substring(colon + 1));
664                     } catch (NumberFormatException JavaDoc e) {
665                         throw new MalformedURLException JavaDoc(e.toString());
666                     }
667                     host = authority.substring(at+1, colon);
668                 } else {
669                     host = authority.substring(at+1);
670                     port = -1;
671                 }
672             }
673         }
674
675         // Parse the path section
676
if (spec.indexOf("/", start) == start) { // Absolute path
677
path = spec.substring(start, limit);
678             if (query != null)
679                 file = path + "?" + query;
680             else
681                 file = path;
682             return;
683         }
684
685         // Resolve relative path against our context's file
686
if (path == null) {
687             if (query != null)
688                 file = "?" + query;
689             else
690                 file = null;
691             return;
692         }
693         if (!path.startsWith("/"))
694             throw new MalformedURLException JavaDoc
695                 ("Base path does not start with '/'");
696         if (!path.endsWith("/"))
697             path += "/../";
698         path += spec.substring(start, limit);
699         if (query != null)
700             file = path + "?" + query;
701         else
702             file = path;
703         return;
704
705     }
706
707
708 }
709
Popular Tags