URI.java revision f5597e626ecf7949d249dea08c1a2964d890ec11
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 18package java.net; 19 20import java.io.IOException; 21import java.io.ObjectInputStream; 22import java.io.ObjectOutputStream; 23import java.io.Serializable; 24import java.io.UnsupportedEncodingException; 25import java.util.StringTokenizer; 26 27import org.apache.harmony.luni.util.Msg; 28 29/** 30 * This class represents an instance of a URI as defined by RFC 2396. 31 */ 32public final class URI implements Comparable<URI>, Serializable { 33 34 private static final long serialVersionUID = -6052424284110960213l; 35 36 static final String unreserved = "_-!.~\'()*"; //$NON-NLS-1$ 37 38 static final String punct = ",;:$&+="; //$NON-NLS-1$ 39 40 static final String reserved = punct + "?/[]@"; //$NON-NLS-1$ 41 42 static final String someLegal = unreserved + punct; 43 44 static final String allLegal = unreserved + reserved; 45 46 private String string; 47 48 private transient String scheme; 49 50 private transient String schemespecificpart; 51 52 private transient String authority; 53 54 private transient String userinfo; 55 56 private transient String host; 57 58 private transient int port = -1; 59 60 private transient String path; 61 62 private transient String query; 63 64 private transient String fragment; 65 66 private transient boolean opaque; 67 68 private transient boolean absolute; 69 70 private transient boolean serverAuthority = false; 71 72 private transient int hash = -1; 73 74 private URI() { 75 } 76 77 /** 78 * Creates a new URI instance according to the given string {@code uri}. 79 * 80 * @param uri 81 * the textual URI representation to be parsed into a URI object. 82 * @throws URISyntaxException 83 * if the given string {@code uri} doesn't fit to the 84 * specification RFC2396 or could not be parsed correctly. 85 */ 86 public URI(String uri) throws URISyntaxException { 87 new Helper().parseURI(uri, false); 88 } 89 90 /** 91 * Creates a new URI instance using the given arguments. This constructor 92 * first creates a temporary URI string from the given components. This 93 * string will be parsed later on to create the URI instance. 94 * <p> 95 * {@code [scheme:]scheme-specific-part[#fragment]} 96 * 97 * @param scheme 98 * the scheme part of the URI. 99 * @param ssp 100 * the scheme-specific-part of the URI. 101 * @param frag 102 * the fragment part of the URI. 103 * @throws URISyntaxException 104 * if the temporary created string doesn't fit to the 105 * specification RFC2396 or could not be parsed correctly. 106 */ 107 public URI(String scheme, String ssp, String frag) 108 throws URISyntaxException { 109 StringBuffer uri = new StringBuffer(); 110 if (scheme != null) { 111 uri.append(scheme); 112 uri.append(':'); 113 } 114 if (ssp != null) { 115 // QUOTE ILLEGAL CHARACTERS 116 uri.append(quoteComponent(ssp, allLegal)); 117 } 118 if (frag != null) { 119 uri.append('#'); 120 // QUOTE ILLEGAL CHARACTERS 121 uri.append(quoteComponent(frag, allLegal)); 122 } 123 124 new Helper().parseURI(uri.toString(), false); 125 } 126 127 /** 128 * Creates a new URI instance using the given arguments. This constructor 129 * first creates a temporary URI string from the given components. This 130 * string will be parsed later on to create the URI instance. 131 * <p> 132 * {@code [scheme:][user-info@]host[:port][path][?query][#fragment]} 133 * 134 * @param scheme 135 * the scheme part of the URI. 136 * @param userinfo 137 * the user information of the URI for authentication and 138 * authorization. 139 * @param host 140 * the host name of the URI. 141 * @param port 142 * the port number of the URI. 143 * @param path 144 * the path to the resource on the host. 145 * @param query 146 * the query part of the URI to specify parameters for the 147 * resource. 148 * @param fragment 149 * the fragment part of the URI. 150 * @throws URISyntaxException 151 * if the temporary created string doesn't fit to the 152 * specification RFC2396 or could not be parsed correctly. 153 */ 154 public URI(String scheme, String userinfo, String host, int port, 155 String path, String query, String fragment) 156 throws URISyntaxException { 157 158 if (scheme == null && userinfo == null && host == null && path == null 159 && query == null && fragment == null) { 160 this.path = ""; //$NON-NLS-1$ 161 return; 162 } 163 164 if (scheme != null && path != null && path.length() > 0 165 && path.charAt(0) != '/') { 166 throw new URISyntaxException(path, Msg.getString("K0302")); //$NON-NLS-1$ 167 } 168 169 StringBuffer uri = new StringBuffer(); 170 if (scheme != null) { 171 uri.append(scheme); 172 uri.append(':'); 173 } 174 175 if (userinfo != null || host != null || port != -1) { 176 uri.append("//"); //$NON-NLS-1$ 177 } 178 179 if (userinfo != null) { 180 // QUOTE ILLEGAL CHARACTERS in userinfo 181 uri.append(quoteComponent(userinfo, someLegal)); 182 uri.append('@'); 183 } 184 185 if (host != null) { 186 // check for ipv6 addresses that hasn't been enclosed 187 // in square brackets 188 if (host.indexOf(':') != -1 && host.indexOf(']') == -1 189 && host.indexOf('[') == -1) { 190 host = "[" + host + "]"; //$NON-NLS-1$ //$NON-NLS-2$ 191 } 192 uri.append(host); 193 } 194 195 if (port != -1) { 196 uri.append(':'); 197 uri.append(port); 198 } 199 200 if (path != null) { 201 // QUOTE ILLEGAL CHARS 202 uri.append(quoteComponent(path, "/@" + someLegal)); //$NON-NLS-1$ 203 } 204 205 if (query != null) { 206 uri.append('?'); 207 // QUOTE ILLEGAL CHARS 208 uri.append(quoteComponent(query, allLegal)); 209 } 210 211 if (fragment != null) { 212 // QUOTE ILLEGAL CHARS 213 uri.append('#'); 214 uri.append(quoteComponent(fragment, allLegal)); 215 } 216 217 new Helper().parseURI(uri.toString(), true); 218 } 219 220 /** 221 * Creates a new URI instance using the given arguments. This constructor 222 * first creates a temporary URI string from the given components. This 223 * string will be parsed later on to create the URI instance. 224 * <p> 225 * {@code [scheme:]host[path][#fragment]} 226 * 227 * @param scheme 228 * the scheme part of the URI. 229 * @param host 230 * the host name of the URI. 231 * @param path 232 * the path to the resource on the host. 233 * @param fragment 234 * the fragment part of the URI. 235 * @throws URISyntaxException 236 * if the temporary created string doesn't fit to the 237 * specification RFC2396 or could not be parsed correctly. 238 */ 239 public URI(String scheme, String host, String path, String fragment) 240 throws URISyntaxException { 241 this(scheme, null, host, -1, path, null, fragment); 242 } 243 244 /** 245 * Creates a new URI instance using the given arguments. This constructor 246 * first creates a temporary URI string from the given components. This 247 * string will be parsed later on to create the URI instance. 248 * <p> 249 * {@code [scheme:][//authority][path][?query][#fragment]} 250 * 251 * @param scheme 252 * the scheme part of the URI. 253 * @param authority 254 * the authority part of the URI. 255 * @param path 256 * the path to the resource on the host. 257 * @param query 258 * the query part of the URI to specify parameters for the 259 * resource. 260 * @param fragment 261 * the fragment part of the URI. 262 * @throws URISyntaxException 263 * if the temporary created string doesn't fit to the 264 * specification RFC2396 or could not be parsed correctly. 265 */ 266 public URI(String scheme, String authority, String path, String query, 267 String fragment) throws URISyntaxException { 268 if (scheme != null && path != null && path.length() > 0 269 && path.charAt(0) != '/') { 270 throw new URISyntaxException(path, Msg.getString("K0302")); //$NON-NLS-1$ 271 } 272 273 StringBuffer uri = new StringBuffer(); 274 if (scheme != null) { 275 uri.append(scheme); 276 uri.append(':'); 277 } 278 if (authority != null) { 279 uri.append("//"); //$NON-NLS-1$ 280 // QUOTE ILLEGAL CHARS 281 uri.append(quoteComponent(authority, "@[]" + someLegal)); //$NON-NLS-1$ 282 } 283 284 if (path != null) { 285 // QUOTE ILLEGAL CHARS 286 uri.append(quoteComponent(path, "/@" + someLegal)); //$NON-NLS-1$ 287 } 288 if (query != null) { 289 // QUOTE ILLEGAL CHARS 290 uri.append('?'); 291 uri.append(quoteComponent(query, allLegal)); 292 } 293 if (fragment != null) { 294 // QUOTE ILLEGAL CHARS 295 uri.append('#'); 296 uri.append(quoteComponent(fragment, allLegal)); 297 } 298 299 new Helper().parseURI(uri.toString(), false); 300 } 301 302 private class Helper { 303 304 private void parseURI(String uri, boolean forceServer) 305 throws URISyntaxException { 306 String temp = uri; 307 // assign uri string to the input value per spec 308 string = uri; 309 int index, index1, index2, index3; 310 // parse into Fragment, Scheme, and SchemeSpecificPart 311 // then parse SchemeSpecificPart if necessary 312 313 // Fragment 314 index = temp.indexOf('#'); 315 if (index != -1) { 316 // remove the fragment from the end 317 fragment = temp.substring(index + 1); 318 validateFragment(uri, fragment, index + 1); 319 temp = temp.substring(0, index); 320 } 321 322 // Scheme and SchemeSpecificPart 323 index = index1 = temp.indexOf(':'); 324 index2 = temp.indexOf('/'); 325 index3 = temp.indexOf('?'); 326 327 // if a '/' or '?' occurs before the first ':' the uri has no 328 // specified scheme, and is therefore not absolute 329 if (index != -1 && (index2 >= index || index2 == -1) 330 && (index3 >= index || index3 == -1)) { 331 // the characters up to the first ':' comprise the scheme 332 absolute = true; 333 scheme = temp.substring(0, index); 334 if (scheme.length() == 0) { 335 throw new URISyntaxException(uri, Msg.getString("K0342"), //$NON-NLS-1$ 336 index); 337 } 338 validateScheme(uri, scheme, 0); 339 schemespecificpart = temp.substring(index + 1); 340 if (schemespecificpart.length() == 0) { 341 throw new URISyntaxException(uri, Msg.getString("K0303"), //$NON-NLS-1$ 342 index + 1); 343 } 344 } else { 345 absolute = false; 346 schemespecificpart = temp; 347 } 348 349 if (scheme == null || schemespecificpart.length() > 0 350 && schemespecificpart.charAt(0) == '/') { 351 opaque = false; 352 // the URI is hierarchical 353 354 // Query 355 temp = schemespecificpart; 356 index = temp.indexOf('?'); 357 if (index != -1) { 358 query = temp.substring(index + 1); 359 temp = temp.substring(0, index); 360 validateQuery(uri, query, index2 + 1 + index); 361 } 362 363 // Authority and Path 364 if (temp.startsWith("//")) { //$NON-NLS-1$ 365 index = temp.indexOf('/', 2); 366 if (index != -1) { 367 authority = temp.substring(2, index); 368 path = temp.substring(index); 369 } else { 370 authority = temp.substring(2); 371 if (authority.length() == 0 && query == null 372 && fragment == null) { 373 throw new URISyntaxException(uri, Msg 374 .getString("K0304"), uri.length()); //$NON-NLS-1$ 375 } 376 377 path = ""; //$NON-NLS-1$ 378 // nothing left, so path is empty (not null, path should 379 // never be null) 380 } 381 382 if (authority.length() == 0) { 383 authority = null; 384 } else { 385 validateAuthority(uri, authority, index1 + 3); 386 } 387 } else { // no authority specified 388 path = temp; 389 } 390 391 int pathIndex = 0; 392 if (index2 > -1) { 393 pathIndex += index2; 394 } 395 if (index > -1) { 396 pathIndex += index; 397 } 398 validatePath(uri, path, pathIndex); 399 } else { // if not hierarchical, URI is opaque 400 opaque = true; 401 validateSsp(uri, schemespecificpart, index2 + 2 + index); 402 } 403 404 parseAuthority(forceServer); 405 } 406 407 private void validateScheme(String uri, String scheme, int index) 408 throws URISyntaxException { 409 // first char needs to be an alpha char 410 char ch = scheme.charAt(0); 411 if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) { 412 throw new URISyntaxException(uri, Msg.getString("K0305"), 0); //$NON-NLS-1$ 413 } 414 415 try { 416 URIEncoderDecoder.validateSimple(scheme, "+-."); //$NON-NLS-1$ 417 } catch (URISyntaxException e) { 418 throw new URISyntaxException(uri, Msg.getString("K0305"), index //$NON-NLS-1$ 419 + e.getIndex()); 420 } 421 } 422 423 private void validateSsp(String uri, String ssp, int index) 424 throws URISyntaxException { 425 try { 426 URIEncoderDecoder.validate(ssp, allLegal); 427 } catch (URISyntaxException e) { 428 throw new URISyntaxException(uri, Msg.getString("K0306", e //$NON-NLS-1$ 429 .getReason()), index + e.getIndex()); 430 } 431 } 432 433 private void validateAuthority(String uri, String authority, int index) 434 throws URISyntaxException { 435 try { 436 URIEncoderDecoder.validate(authority, "@[]" + someLegal); //$NON-NLS-1$ 437 } catch (URISyntaxException e) { 438 throw new URISyntaxException(uri, Msg.getString("K0307", e //$NON-NLS-1$ 439 .getReason()), index + e.getIndex()); 440 } 441 } 442 443 private void validatePath(String uri, String path, int index) 444 throws URISyntaxException { 445 try { 446 URIEncoderDecoder.validate(path, "/@" + someLegal); //$NON-NLS-1$ 447 } catch (URISyntaxException e) { 448 throw new URISyntaxException(uri, Msg.getString("K0308", e //$NON-NLS-1$ 449 .getReason()), index + e.getIndex()); 450 } 451 } 452 453 private void validateQuery(String uri, String query, int index) 454 throws URISyntaxException { 455 try { 456 URIEncoderDecoder.validate(query, allLegal); 457 } catch (URISyntaxException e) { 458 throw new URISyntaxException(uri, Msg.getString("K0309", e //$NON-NLS-1$ 459 .getReason()), index + e.getIndex()); 460 461 } 462 } 463 464 private void validateFragment(String uri, String fragment, int index) 465 throws URISyntaxException { 466 try { 467 URIEncoderDecoder.validate(fragment, allLegal); 468 } catch (URISyntaxException e) { 469 throw new URISyntaxException(uri, Msg.getString("K030a", e //$NON-NLS-1$ 470 .getReason()), index + e.getIndex()); 471 } 472 } 473 474 /** 475 * determine the host, port and userinfo if the authority parses 476 * successfully to a server based authority 477 * 478 * behavour in error cases: if forceServer is true, throw 479 * URISyntaxException with the proper diagnostic messages. if 480 * forceServer is false assume this is a registry based uri, and just 481 * return leaving the host, port and userinfo fields undefined. 482 * 483 * and there are some error cases where URISyntaxException is thrown 484 * regardless of the forceServer parameter e.g. malformed ipv6 address 485 */ 486 private void parseAuthority(boolean forceServer) 487 throws URISyntaxException { 488 if (authority == null) { 489 return; 490 } 491 492 String temp, tempUserinfo = null, tempHost = null; 493 int index, hostindex = 0; 494 int tempPort = -1; 495 496 temp = authority; 497 index = temp.indexOf('@'); 498 if (index != -1) { 499 // remove user info 500 tempUserinfo = temp.substring(0, index); 501 validateUserinfo(authority, tempUserinfo, 0); 502 temp = temp.substring(index + 1); // host[:port] is left 503 hostindex = index + 1; 504 } 505 506 index = temp.lastIndexOf(':'); 507 int endindex = temp.indexOf(']'); 508 509 if (index != -1 && endindex < index) { 510 // determine port and host 511 tempHost = temp.substring(0, index); 512 513 if (index < (temp.length() - 1)) { // port part is not empty 514 try { 515 tempPort = Integer.parseInt(temp.substring(index + 1)); 516 if (tempPort < 0) { 517 if (forceServer) { 518 throw new URISyntaxException( 519 authority, 520 Msg.getString("K00b1"), hostindex + index + 1); //$NON-NLS-1$ 521 } 522 return; 523 } 524 } catch (NumberFormatException e) { 525 if (forceServer) { 526 throw new URISyntaxException(authority, Msg 527 .getString("K00b1"), hostindex + index + 1); //$NON-NLS-1$ 528 } 529 return; 530 } 531 } 532 } else { 533 tempHost = temp; 534 } 535 536 if (tempHost.equals("")) { //$NON-NLS-1$ 537 if (forceServer) { 538 throw new URISyntaxException(authority, Msg 539 .getString("K030c"), hostindex); //$NON-NLS-1$ 540 } 541 return; 542 } 543 544 if (!isValidHost(forceServer, tempHost)) { 545 return; 546 } 547 548 // this is a server based uri, 549 // fill in the userinfo, host and port fields 550 userinfo = tempUserinfo; 551 host = tempHost; 552 port = tempPort; 553 serverAuthority = true; 554 } 555 556 private void validateUserinfo(String uri, String userinfo, int index) 557 throws URISyntaxException { 558 for (int i = 0; i < userinfo.length(); i++) { 559 char ch = userinfo.charAt(i); 560 if (ch == ']' || ch == '[') { 561 throw new URISyntaxException(uri, Msg.getString("K030d"), //$NON-NLS-1$ 562 index + i); 563 } 564 } 565 } 566 567 /** 568 * distinguish between IPv4, IPv6, domain name and validate it based on 569 * its type 570 */ 571 private boolean isValidHost(boolean forceServer, String host) 572 throws URISyntaxException { 573 if (host.charAt(0) == '[') { 574 // ipv6 address 575 if (host.charAt(host.length() - 1) != ']') { 576 throw new URISyntaxException(host, 577 Msg.getString("K030e"), 0); //$NON-NLS-1$ 578 } 579 if (!isValidIP6Address(host)) { 580 throw new URISyntaxException(host, Msg.getString("K030f")); //$NON-NLS-1$ 581 } 582 return true; 583 } 584 585 // '[' and ']' can only be the first char and last char 586 // of the host name 587 if (host.indexOf('[') != -1 || host.indexOf(']') != -1) { 588 throw new URISyntaxException(host, Msg.getString("K0310"), 0); //$NON-NLS-1$ 589 } 590 591 int index = host.lastIndexOf('.'); 592 if (index < 0 || index == host.length() - 1 593 || !Character.isDigit(host.charAt(index + 1))) { 594 // domain name 595 if (isValidDomainName(host)) { 596 return true; 597 } 598 if (forceServer) { 599 throw new URISyntaxException(host, 600 Msg.getString("K0310"), 0); //$NON-NLS-1$ 601 } 602 return false; 603 } 604 605 // IPv4 address 606 if (isValidIPv4Address(host)) { 607 return true; 608 } 609 if (forceServer) { 610 throw new URISyntaxException(host, Msg.getString("K0311"), 0); //$NON-NLS-1$ 611 } 612 return false; 613 } 614 615 private boolean isValidDomainName(String host) { 616 try { 617 URIEncoderDecoder.validateSimple(host, "-."); //$NON-NLS-1$ 618 } catch (URISyntaxException e) { 619 return false; 620 } 621 622 String label = null; 623 StringTokenizer st = new StringTokenizer(host, "."); //$NON-NLS-1$ 624 while (st.hasMoreTokens()) { 625 label = st.nextToken(); 626 if (label.startsWith("-") || label.endsWith("-")) { //$NON-NLS-1$ //$NON-NLS-2$ 627 return false; 628 } 629 } 630 631 if (!label.equals(host)) { 632 char ch = label.charAt(0); 633 if (ch >= '0' && ch <= '9') { 634 return false; 635 } 636 } 637 return true; 638 } 639 640 private boolean isValidIPv4Address(String host) { 641 int index; 642 int index2; 643 try { 644 int num; 645 index = host.indexOf('.'); 646 num = Integer.parseInt(host.substring(0, index)); 647 if (num < 0 || num > 255) { 648 return false; 649 } 650 index2 = host.indexOf('.', index + 1); 651 num = Integer.parseInt(host.substring(index + 1, index2)); 652 if (num < 0 || num > 255) { 653 return false; 654 } 655 index = host.indexOf('.', index2 + 1); 656 num = Integer.parseInt(host.substring(index2 + 1, index)); 657 if (num < 0 || num > 255) { 658 return false; 659 } 660 num = Integer.parseInt(host.substring(index + 1)); 661 if (num < 0 || num > 255) { 662 return false; 663 } 664 } catch (Exception e) { 665 return false; 666 } 667 return true; 668 } 669 670 private boolean isValidIP6Address(String ipAddress) { 671 int length = ipAddress.length(); 672 boolean doubleColon = false; 673 int numberOfColons = 0; 674 int numberOfPeriods = 0; 675 String word = ""; //$NON-NLS-1$ 676 char c = 0; 677 char prevChar = 0; 678 int offset = 0; // offset for [] ip addresses 679 680 if (length < 2) { 681 return false; 682 } 683 684 for (int i = 0; i < length; i++) { 685 prevChar = c; 686 c = ipAddress.charAt(i); 687 switch (c) { 688 689 // case for an open bracket [x:x:x:...x] 690 case '[': 691 if (i != 0) { 692 return false; // must be first character 693 } 694 if (ipAddress.charAt(length - 1) != ']') { 695 return false; // must have a close ] 696 } 697 if ((ipAddress.charAt(1) == ':') 698 && (ipAddress.charAt(2) != ':')) { 699 return false; 700 } 701 offset = 1; 702 if (length < 4) { 703 return false; 704 } 705 break; 706 707 // case for a closed bracket at end of IP [x:x:x:...x] 708 case ']': 709 if (i != length - 1) { 710 return false; // must be last character 711 } 712 if (ipAddress.charAt(0) != '[') { 713 return false; // must have a open [ 714 } 715 break; 716 717 // case for the last 32-bits represented as IPv4 718 // x:x:x:x:x:x:d.d.d.d 719 case '.': 720 numberOfPeriods++; 721 if (numberOfPeriods > 3) { 722 return false; 723 } 724 if (!isValidIP4Word(word)) { 725 return false; 726 } 727 if (numberOfColons != 6 && !doubleColon) { 728 return false; 729 } 730 // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons 731 // with 732 // an IPv4 ending, otherwise 7 :'s is bad 733 if (numberOfColons == 7 734 && ipAddress.charAt(0 + offset) != ':' 735 && ipAddress.charAt(1 + offset) != ':') { 736 return false; 737 } 738 word = ""; //$NON-NLS-1$ 739 break; 740 741 case ':': 742 numberOfColons++; 743 if (numberOfColons > 7) { 744 return false; 745 } 746 if (numberOfPeriods > 0) { 747 return false; 748 } 749 if (prevChar == ':') { 750 if (doubleColon) { 751 return false; 752 } 753 doubleColon = true; 754 } 755 word = ""; //$NON-NLS-1$ 756 break; 757 758 default: 759 if (word.length() > 3) { 760 return false; 761 } 762 if (!isValidHexChar(c)) { 763 return false; 764 } 765 word += c; 766 } 767 } 768 769 // Check if we have an IPv4 ending 770 if (numberOfPeriods > 0) { 771 if (numberOfPeriods != 3 || !isValidIP4Word(word)) { 772 return false; 773 } 774 } else { 775 // If we're at then end and we haven't had 7 colons then there 776 // is a problem unless we encountered a doubleColon 777 if (numberOfColons != 7 && !doubleColon) { 778 return false; 779 } 780 781 // If we have an empty word at the end, it means we ended in 782 // either a : or a . 783 // If we did not end in :: then this is invalid 784 if (word == "" && ipAddress.charAt(length - 1 - offset) != ':' //$NON-NLS-1$ 785 && ipAddress.charAt(length - 2 - offset) != ':') { 786 return false; 787 } 788 } 789 790 return true; 791 } 792 793 private boolean isValidIP4Word(String word) { 794 char c; 795 if (word.length() < 1 || word.length() > 3) { 796 return false; 797 } 798 for (int i = 0; i < word.length(); i++) { 799 c = word.charAt(i); 800 if (!(c >= '0' && c <= '9')) { 801 return false; 802 } 803 } 804 if (Integer.parseInt(word) > 255) { 805 return false; 806 } 807 return true; 808 } 809 810 private boolean isValidHexChar(char c) { 811 812 return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') 813 || (c >= 'a' && c <= 'f'); 814 } 815 } 816 817 /* 818 * Quote illegal chars for each component, but not the others 819 * 820 * @param component java.lang.String the component to be converted @param 821 * legalset java.lang.String the legal character set allowed in the 822 * component s @return java.lang.String the converted string 823 */ 824 private String quoteComponent(String component, String legalset) { 825 try { 826 /* 827 * Use a different encoder than URLEncoder since: 1. chars like "/", 828 * "#", "@" etc needs to be preserved instead of being encoded, 2. 829 * UTF-8 char set needs to be used for encoding instead of default 830 * platform one 831 */ 832 return URIEncoderDecoder.quoteIllegal(component, legalset); 833 } catch (UnsupportedEncodingException e) { 834 throw new RuntimeException(e.toString()); 835 } 836 } 837 838 /** 839 * Compares this URI with the given argument {@code uri}. This method will 840 * return a negative value if this URI instance is less than the given 841 * argument and a positive value if this URI instance is greater than the 842 * given argument. The return value {@code 0} indicates that the two 843 * instances represent the same URI. To define the order the single parts of 844 * the URI are compared with each other. String components will be orderer 845 * in the natural case-sensitive way. A hierarchical URI is less than an 846 * opaque URI and if one part is {@code null} the URI with the undefined 847 * part is less than the other one. 848 * 849 * @param uri 850 * the URI this instance has to compare with. 851 * @return the value representing the order of the two instances. 852 */ 853 public int compareTo(URI uri) { 854 int ret = 0; 855 856 // compare schemes 857 if (scheme == null && uri.scheme != null) { 858 return -1; 859 } else if (scheme != null && uri.scheme == null) { 860 return 1; 861 } else if (scheme != null && uri.scheme != null) { 862 ret = scheme.compareToIgnoreCase(uri.scheme); 863 if (ret != 0) { 864 return ret; 865 } 866 } 867 868 // compare opacities 869 if (!opaque && uri.opaque) { 870 return -1; 871 } else if (opaque && !uri.opaque) { 872 return 1; 873 } else if (opaque && uri.opaque) { 874 ret = schemespecificpart.compareTo(uri.schemespecificpart); 875 if (ret != 0) { 876 return ret; 877 } 878 } else { 879 880 // otherwise both must be hierarchical 881 882 // compare authorities 883 if (authority != null && uri.authority == null) { 884 return 1; 885 } else if (authority == null && uri.authority != null) { 886 return -1; 887 } else if (authority != null && uri.authority != null) { 888 if (host != null && uri.host != null) { 889 // both are server based, so compare userinfo, host, port 890 if (userinfo != null && uri.userinfo == null) { 891 return 1; 892 } else if (userinfo == null && uri.userinfo != null) { 893 return -1; 894 } else if (userinfo != null && uri.userinfo != null) { 895 ret = userinfo.compareTo(uri.userinfo); 896 if (ret != 0) { 897 return ret; 898 } 899 } 900 901 // userinfo's are the same, compare hostname 902 ret = host.compareToIgnoreCase(uri.host); 903 if (ret != 0) { 904 return ret; 905 } 906 907 // compare port 908 if (port != uri.port) { 909 return port - uri.port; 910 } 911 } else { // one or both are registry based, compare the whole 912 // authority 913 ret = authority.compareTo(uri.authority); 914 if (ret != 0) { 915 return ret; 916 } 917 } 918 } 919 920 // authorities are the same 921 // compare paths 922 ret = path.compareTo(uri.path); 923 if (ret != 0) { 924 return ret; 925 } 926 927 // compare queries 928 929 if (query != null && uri.query == null) { 930 return 1; 931 } else if (query == null && uri.query != null) { 932 return -1; 933 } else if (query != null && uri.query != null) { 934 ret = query.compareTo(uri.query); 935 if (ret != 0) { 936 return ret; 937 } 938 } 939 } 940 941 // everything else is identical, so compare fragments 942 if (fragment != null && uri.fragment == null) { 943 return 1; 944 } else if (fragment == null && uri.fragment != null) { 945 return -1; 946 } else if (fragment != null && uri.fragment != null) { 947 ret = fragment.compareTo(uri.fragment); 948 if (ret != 0) { 949 return ret; 950 } 951 } 952 953 // identical 954 return 0; 955 } 956 957 /** 958 * Parses the given argument {@code uri} and creates an appropriate URI 959 * instance. 960 * 961 * @param uri 962 * the string which has to be parsed to create the URI instance. 963 * @return the created instance representing the given URI. 964 */ 965 public static URI create(String uri) { 966 URI result = null; 967 try { 968 result = new URI(uri); 969 } catch (URISyntaxException e) { 970 throw new IllegalArgumentException(e.getMessage()); 971 } 972 return result; 973 } 974 975 private URI duplicate() { 976 URI clone = new URI(); 977 clone.absolute = absolute; 978 clone.authority = authority; 979 clone.fragment = fragment; 980 clone.host = host; 981 clone.opaque = opaque; 982 clone.path = path; 983 clone.port = port; 984 clone.query = query; 985 clone.scheme = scheme; 986 clone.schemespecificpart = schemespecificpart; 987 clone.userinfo = userinfo; 988 clone.serverAuthority = serverAuthority; 989 return clone; 990 } 991 992 /* 993 * Takes a string that may contain hex sequences like %F1 or %2b and 994 * converts the hex values following the '%' to lowercase 995 */ 996 private String convertHexToLowerCase(String s) { 997 StringBuffer result = new StringBuffer(""); //$NON-NLS-1$ 998 if (s.indexOf('%') == -1) { 999 return s; 1000 } 1001 1002 int index = 0, previndex = 0; 1003 while ((index = s.indexOf('%', previndex)) != -1) { 1004 result.append(s.substring(previndex, index + 1)); 1005 result.append(s.substring(index + 1, index + 3).toLowerCase()); 1006 index += 3; 1007 previndex = index; 1008 } 1009 return result.toString(); 1010 } 1011 1012 /* 1013 * Takes two strings that may contain hex sequences like %F1 or %2b and 1014 * compares them, ignoring case for the hex values hex values must always 1015 * occur in pairs like above 1016 */ 1017 private boolean equalsHexCaseInsensitive(String first, String second) { 1018 if (first.indexOf('%') != second.indexOf('%')) { 1019 return first.equals(second); 1020 } 1021 1022 int index = 0, previndex = 0; 1023 while ((index = first.indexOf('%', previndex)) != -1 1024 && second.indexOf('%', previndex) == index) { 1025 boolean match = first.substring(previndex, index).equals( 1026 second.substring(previndex, index)); 1027 if (!match) { 1028 return false; 1029 } 1030 1031 match = first.substring(index + 1, index + 3).equalsIgnoreCase( 1032 second.substring(index + 1, index + 3)); 1033 if (!match) { 1034 return false; 1035 } 1036 1037 index += 3; 1038 previndex = index; 1039 } 1040 return first.substring(previndex).equals(second.substring(previndex)); 1041 } 1042 1043 /** 1044 * Compares this URI instance with the given argument {@code o} and 1045 * determines if both are equal. Two URI instances are equal if all single 1046 * parts are identical in their meaning. 1047 * 1048 * @param o 1049 * the URI this instance has to be compared with. 1050 * @return {@code true} if both URI instances point to the same resource, 1051 * {@code false} otherwise. 1052 */ 1053 @Override 1054 public boolean equals(Object o) { 1055 if (!(o instanceof URI)) { 1056 return false; 1057 } 1058 URI uri = (URI) o; 1059 1060 if (uri.fragment == null && fragment != null || uri.fragment != null 1061 && fragment == null) { 1062 return false; 1063 } else if (uri.fragment != null && fragment != null) { 1064 if (!equalsHexCaseInsensitive(uri.fragment, fragment)) { 1065 return false; 1066 } 1067 } 1068 1069 if (uri.scheme == null && scheme != null || uri.scheme != null 1070 && scheme == null) { 1071 return false; 1072 } else if (uri.scheme != null && scheme != null) { 1073 if (!uri.scheme.equalsIgnoreCase(scheme)) { 1074 return false; 1075 } 1076 } 1077 1078 if (uri.opaque && opaque) { 1079 return equalsHexCaseInsensitive(uri.schemespecificpart, 1080 schemespecificpart); 1081 } else if (!uri.opaque && !opaque) { 1082 if (!equalsHexCaseInsensitive(path, uri.path)) { 1083 return false; 1084 } 1085 1086 if (uri.query != null && query == null || uri.query == null 1087 && query != null) { 1088 return false; 1089 } else if (uri.query != null && query != null) { 1090 if (!equalsHexCaseInsensitive(uri.query, query)) { 1091 return false; 1092 } 1093 } 1094 1095 if (uri.authority != null && authority == null 1096 || uri.authority == null && authority != null) { 1097 return false; 1098 } else if (uri.authority != null && authority != null) { 1099 if (uri.host != null && host == null || uri.host == null 1100 && host != null) { 1101 return false; 1102 } else if (uri.host == null && host == null) { 1103 // both are registry based, so compare the whole authority 1104 return equalsHexCaseInsensitive(uri.authority, authority); 1105 } else { // uri.host != null && host != null, so server-based 1106 if (!host.equalsIgnoreCase(uri.host)) { 1107 return false; 1108 } 1109 1110 if (port != uri.port) { 1111 return false; 1112 } 1113 1114 if (uri.userinfo != null && userinfo == null 1115 || uri.userinfo == null && userinfo != null) { 1116 return false; 1117 } else if (uri.userinfo != null && userinfo != null) { 1118 return equalsHexCaseInsensitive(userinfo, uri.userinfo); 1119 } else { 1120 return true; 1121 } 1122 } 1123 } else { 1124 // no authority 1125 return true; 1126 } 1127 1128 } else { 1129 // one is opaque, the other hierarchical 1130 return false; 1131 } 1132 } 1133 1134 /** 1135 * Gets the decoded authority part of this URI. 1136 * 1137 * @return the decoded authority part or {@code null} if undefined. 1138 */ 1139 public String getAuthority() { 1140 return decode(authority); 1141 } 1142 1143 /** 1144 * Gets the decoded fragment part of this URI. 1145 * 1146 * @return the decoded fragment part or {@code null} if undefined. 1147 */ 1148 public String getFragment() { 1149 return decode(fragment); 1150 } 1151 1152 /** 1153 * Gets the host part of this URI. 1154 * 1155 * @return the host part or {@code null} if undefined. 1156 */ 1157 public String getHost() { 1158 return host; 1159 } 1160 1161 /** 1162 * Gets the decoded path part of this URI. 1163 * 1164 * @return the decoded path part or {@code null} if undefined. 1165 */ 1166 public String getPath() { 1167 return decode(path); 1168 } 1169 1170 /** 1171 * Gets the port number of this URI. 1172 * 1173 * @return the port number or {@code -1} if undefined. 1174 */ 1175 public int getPort() { 1176 return port; 1177 } 1178 1179 /** 1180 * Gets the decoded query part of this URI. 1181 * 1182 * @return the decoded query part or {@code null} if undefined. 1183 */ 1184 public String getQuery() { 1185 return decode(query); 1186 } 1187 1188 /** 1189 * Gets the authority part of this URI in raw form. 1190 * 1191 * @return the encoded authority part or {@code null} if undefined. 1192 */ 1193 public String getRawAuthority() { 1194 return authority; 1195 } 1196 1197 /** 1198 * Gets the fragment part of this URI in raw form. 1199 * 1200 * @return the encoded fragment part or {@code null} if undefined. 1201 */ 1202 public String getRawFragment() { 1203 return fragment; 1204 } 1205 1206 /** 1207 * Gets the path part of this URI in raw form. 1208 * 1209 * @return the encoded path part or {@code null} if undefined. 1210 */ 1211 public String getRawPath() { 1212 return path; 1213 } 1214 1215 /** 1216 * Gets the query part of this URI in raw form. 1217 * 1218 * @return the encoded query part or {@code null} if undefined. 1219 */ 1220 public String getRawQuery() { 1221 return query; 1222 } 1223 1224 /** 1225 * Gets the scheme-specific part of this URI in raw form. 1226 * 1227 * @return the encoded scheme-specific part or {@code null} if undefined. 1228 */ 1229 public String getRawSchemeSpecificPart() { 1230 return schemespecificpart; 1231 } 1232 1233 /** 1234 * Gets the user-info part of this URI in raw form. 1235 * 1236 * @return the encoded user-info part or {@code null} if undefined. 1237 */ 1238 public String getRawUserInfo() { 1239 return userinfo; 1240 } 1241 1242 /** 1243 * Gets the scheme part of this URI. 1244 * 1245 * @return the scheme part or {@code null} if undefined. 1246 */ 1247 public String getScheme() { 1248 return scheme; 1249 } 1250 1251 /** 1252 * Gets the decoded scheme-specific part of this URI. 1253 * 1254 * @return the decoded scheme-specific part or {@code null} if undefined. 1255 */ 1256 public String getSchemeSpecificPart() { 1257 return decode(schemespecificpart); 1258 } 1259 1260 /** 1261 * Gets the decoded user-info part of this URI. 1262 * 1263 * @return the decoded user-info part or {@code null} if undefined. 1264 */ 1265 public String getUserInfo() { 1266 return decode(userinfo); 1267 } 1268 1269 /** 1270 * Gets the hashcode value of this URI instance. 1271 * 1272 * @return the appropriate hashcode value. 1273 */ 1274 @Override 1275 public int hashCode() { 1276 if (hash == -1) { 1277 hash = getHashString().hashCode(); 1278 } 1279 return hash; 1280 } 1281 1282 /** 1283 * Indicates whether this URI is absolute, which means that a scheme part is 1284 * defined in this URI. 1285 * 1286 * @return {@code true} if this URI is absolute, {@code false} otherwise. 1287 */ 1288 public boolean isAbsolute() { 1289 return absolute; 1290 } 1291 1292 /** 1293 * Indicates whether this URI is opaque or not. An opaque URI is absolute 1294 * and has a scheme-specific part which does not start with a slash 1295 * character. All parts except scheme, scheme-specific and fragment are 1296 * undefined. 1297 * 1298 * @return {@code true} if the URI is opaque, {@code false} otherwise. 1299 */ 1300 public boolean isOpaque() { 1301 return opaque; 1302 } 1303 1304 /* 1305 * normalize path, and return the resulting string 1306 */ 1307 private String normalize(String path) { 1308 // count the number of '/'s, to determine number of segments 1309 int index = -1; 1310 int pathlen = path.length(); 1311 int size = 0; 1312 if (pathlen > 0 && path.charAt(0) != '/') { 1313 size++; 1314 } 1315 while ((index = path.indexOf('/', index + 1)) != -1) { 1316 if (index + 1 < pathlen && path.charAt(index + 1) != '/') { 1317 size++; 1318 } 1319 } 1320 1321 String[] seglist = new String[size]; 1322 boolean[] include = new boolean[size]; 1323 1324 // break the path into segments and store in the list 1325 int current = 0; 1326 int index2 = 0; 1327 index = (pathlen > 0 && path.charAt(0) == '/') ? 1 : 0; 1328 while ((index2 = path.indexOf('/', index + 1)) != -1) { 1329 seglist[current++] = path.substring(index, index2); 1330 index = index2 + 1; 1331 } 1332 1333 // if current==size, then the last character was a slash 1334 // and there are no more segments 1335 if (current < size) { 1336 seglist[current] = path.substring(index); 1337 } 1338 1339 // determine which segments get included in the normalized path 1340 for (int i = 0; i < size; i++) { 1341 include[i] = true; 1342 if (seglist[i].equals("..")) { //$NON-NLS-1$ 1343 int remove = i - 1; 1344 // search back to find a segment to remove, if possible 1345 while (remove > -1 && !include[remove]) { 1346 remove--; 1347 } 1348 // if we find a segment to remove, remove it and the ".." 1349 // segment 1350 if (remove > -1 && !seglist[remove].equals("..")) { //$NON-NLS-1$ 1351 include[remove] = false; 1352 include[i] = false; 1353 } 1354 } else if (seglist[i].equals(".")) { //$NON-NLS-1$ 1355 include[i] = false; 1356 } 1357 } 1358 1359 // put the path back together 1360 StringBuffer newpath = new StringBuffer(); 1361 if (path.startsWith("/")) { //$NON-NLS-1$ 1362 newpath.append('/'); 1363 } 1364 1365 for (int i = 0; i < seglist.length; i++) { 1366 if (include[i]) { 1367 newpath.append(seglist[i]); 1368 newpath.append('/'); 1369 } 1370 } 1371 1372 // if we used at least one segment and the path previously ended with 1373 // a slash and the last segment is still used, then delete the extra 1374 // trailing '/' 1375 if (!path.endsWith("/") && seglist.length > 0 //$NON-NLS-1$ 1376 && include[seglist.length - 1]) { 1377 newpath.deleteCharAt(newpath.length() - 1); 1378 } 1379 1380 String result = newpath.toString(); 1381 1382 // check for a ':' in the first segment if one exists, 1383 // prepend "./" to normalize 1384 index = result.indexOf(':'); 1385 index2 = result.indexOf('/'); 1386 if (index != -1 && (index < index2 || index2 == -1)) { 1387 newpath.insert(0, "./"); //$NON-NLS-1$ 1388 result = newpath.toString(); 1389 } 1390 return result; 1391 } 1392 1393 /** 1394 * Normalizes the path part of this URI. 1395 * 1396 * @return an URI object which represents this instance with a normalized 1397 * path. 1398 */ 1399 public URI normalize() { 1400 if (opaque) { 1401 return this; 1402 } 1403 String normalizedPath = normalize(path); 1404 // if the path is already normalized, return this 1405 if (path.equals(normalizedPath)) { 1406 return this; 1407 } 1408 // get an exact copy of the URI re-calculate the scheme specific part 1409 // since the path of the normalized URI is different from this URI. 1410 URI result = duplicate(); 1411 result.path = normalizedPath; 1412 result.setSchemeSpecificPart(); 1413 return result; 1414 } 1415 1416 /** 1417 * Tries to parse the authority component of this URI to divide it into the 1418 * host, port, and user-info. If this URI is already determined as a 1419 * ServerAuthority this instance will be returned without changes. 1420 * 1421 * @return this instance with the components of the parsed server authority. 1422 * @throws URISyntaxException 1423 * if the authority part could not be parsed as a server-based 1424 * authority. 1425 */ 1426 public URI parseServerAuthority() throws URISyntaxException { 1427 if (!serverAuthority) { 1428 new Helper().parseAuthority(true); 1429 } 1430 return this; 1431 } 1432 1433 /** 1434 * Makes the given URI {@code relative} to a relative URI against the URI 1435 * represented by this instance. 1436 * 1437 * @param relative 1438 * the URI which has to be relativized against this URI. 1439 * @return the relative URI. 1440 */ 1441 public URI relativize(URI relative) { 1442 if (relative.opaque || opaque) { 1443 return relative; 1444 } 1445 1446 if (scheme == null ? relative.scheme != null : !scheme 1447 .equals(relative.scheme)) { 1448 return relative; 1449 } 1450 1451 if (authority == null ? relative.authority != null : !authority 1452 .equals(relative.authority)) { 1453 return relative; 1454 } 1455 1456 // normalize both paths 1457 String thisPath = normalize(path); 1458 String relativePath = normalize(relative.path); 1459 1460 /* 1461 * if the paths aren't equal, then we need to determine if this URI's 1462 * path is a parent path (begins with) the relative URI's path 1463 */ 1464 if (!thisPath.equals(relativePath)) { 1465 // if this URI's path doesn't end in a '/', add one 1466 if (!thisPath.endsWith("/")) { //$NON-NLS-1$ 1467 thisPath = thisPath + '/'; 1468 } 1469 /* 1470 * if the relative URI's path doesn't start with this URI's path, 1471 * then just return the relative URI; the URIs have nothing in 1472 * common 1473 */ 1474 if (!relativePath.startsWith(thisPath)) { 1475 return relative; 1476 } 1477 } 1478 1479 URI result = new URI(); 1480 result.fragment = relative.fragment; 1481 result.query = relative.query; 1482 // the result URI is the remainder of the relative URI's path 1483 result.path = relativePath.substring(thisPath.length()); 1484 result.setSchemeSpecificPart(); 1485 return result; 1486 } 1487 1488 /** 1489 * Resolves the given URI {@code relative} against the URI represented by 1490 * this instance. 1491 * 1492 * @param relative 1493 * the URI which has to be resolved against this URI. 1494 * @return the resolved URI. 1495 */ 1496 public URI resolve(URI relative) { 1497 if (relative.absolute || opaque) { 1498 return relative; 1499 } 1500 1501 URI result; 1502 if (relative.path.equals("") && relative.scheme == null //$NON-NLS-1$ 1503 && relative.authority == null && relative.query == null 1504 && relative.fragment != null) { 1505 // if the relative URI only consists of fragment, 1506 // the resolved URI is very similar to this URI, 1507 // except that it has the fragement from the relative URI. 1508 result = duplicate(); 1509 result.fragment = relative.fragment; 1510 // no need to re-calculate the scheme specific part, 1511 // since fragment is not part of scheme specific part. 1512 return result; 1513 } 1514 1515 if (relative.authority != null) { 1516 // if the relative URI has authority, 1517 // the resolved URI is almost the same as the relative URI, 1518 // except that it has the scheme of this URI. 1519 result = relative.duplicate(); 1520 result.scheme = scheme; 1521 result.absolute = absolute; 1522 } else { 1523 // since relative URI has no authority, 1524 // the resolved URI is very similar to this URI, 1525 // except that it has the query and fragment of the relative URI, 1526 // and the path is different. 1527 result = duplicate(); 1528 result.fragment = relative.fragment; 1529 result.query = relative.query; 1530 if (relative.path.startsWith("/")) { //$NON-NLS-1$ 1531 result.path = relative.path; 1532 } else { 1533 // resolve a relative reference 1534 int endindex = path.lastIndexOf('/') + 1; 1535 result.path = normalize(path.substring(0, endindex) 1536 + relative.path); 1537 } 1538 // re-calculate the scheme specific part since 1539 // query and path of the resolved URI is different from this URI. 1540 result.setSchemeSpecificPart(); 1541 } 1542 return result; 1543 } 1544 1545 /** 1546 * Helper method used to re-calculate the scheme specific part of the 1547 * resolved or normalized URIs 1548 */ 1549 private void setSchemeSpecificPart() { 1550 // ssp = [//authority][path][?query] 1551 StringBuffer ssp = new StringBuffer(); 1552 if (authority != null) { 1553 ssp.append("//" + authority); //$NON-NLS-1$ 1554 } 1555 if (path != null) { 1556 ssp.append(path); 1557 } 1558 if (query != null) { 1559 ssp.append("?" + query); //$NON-NLS-1$ 1560 } 1561 schemespecificpart = ssp.toString(); 1562 // reset string, so that it can be re-calculated correctly when asked. 1563 string = null; 1564 } 1565 1566 /** 1567 * Creates a new URI instance by parsing the given string {@code relative} 1568 * and resolves the created URI against the URI represented by this 1569 * instance. 1570 * 1571 * @param relative 1572 * the given string to create the new URI instance which has to 1573 * be resolved later on. 1574 * @return the created and resolved URI. 1575 */ 1576 public URI resolve(String relative) { 1577 return resolve(create(relative)); 1578 } 1579 1580 /* 1581 * Encode unicode chars that are not part of US-ASCII char set into the 1582 * escaped form 1583 * 1584 * i.e. The Euro currency symbol is encoded as "%E2%82%AC". 1585 * 1586 * @param component java.lang.String the component to be converted @param 1587 * legalset java.lang.String the legal character set allowed in the 1588 * component s @return java.lang.String the converted string 1589 */ 1590 private String encodeOthers(String s) { 1591 try { 1592 /* 1593 * Use a different encoder than URLEncoder since: 1. chars like "/", 1594 * "#", "@" etc needs to be preserved instead of being encoded, 2. 1595 * UTF-8 char set needs to be used for encoding instead of default 1596 * platform one 3. Only other chars need to be converted 1597 */ 1598 return URIEncoderDecoder.encodeOthers(s); 1599 } catch (UnsupportedEncodingException e) { 1600 throw new RuntimeException(e.toString()); 1601 } 1602 } 1603 1604 private String decode(String s) { 1605 if (s == null) { 1606 return s; 1607 } 1608 1609 try { 1610 return URIEncoderDecoder.decode(s); 1611 } catch (UnsupportedEncodingException e) { 1612 throw new RuntimeException(e.toString()); 1613 } 1614 } 1615 1616 /** 1617 * Returns the textual string representation of this URI instance using the 1618 * US-ASCII encoding. 1619 * 1620 * @return the US-ASCII string representation of this URI. 1621 */ 1622 public String toASCIIString() { 1623 return encodeOthers(toString()); 1624 } 1625 1626 /** 1627 * Returns the textual string representation of this URI instance. 1628 * 1629 * @return the textual string representation of this URI. 1630 */ 1631 @Override 1632 public String toString() { 1633 if (string == null) { 1634 StringBuffer result = new StringBuffer(); 1635 if (scheme != null) { 1636 result.append(scheme); 1637 result.append(':'); 1638 } 1639 if (opaque) { 1640 result.append(schemespecificpart); 1641 } else { 1642 if (authority != null) { 1643 result.append("//"); //$NON-NLS-1$ 1644 result.append(authority); 1645 } 1646 1647 if (path != null) { 1648 result.append(path); 1649 } 1650 1651 if (query != null) { 1652 result.append('?'); 1653 result.append(query); 1654 } 1655 } 1656 1657 if (fragment != null) { 1658 result.append('#'); 1659 result.append(fragment); 1660 } 1661 1662 string = result.toString(); 1663 } 1664 return string; 1665 } 1666 1667 /* 1668 * Form a string from the components of this URI, similarly to the 1669 * toString() method. But this method converts scheme and host to lowercase, 1670 * and converts escaped octets to lowercase. 1671 */ 1672 private String getHashString() { 1673 StringBuffer result = new StringBuffer(); 1674 if (scheme != null) { 1675 result.append(scheme.toLowerCase()); 1676 result.append(':'); 1677 } 1678 if (opaque) { 1679 result.append(schemespecificpart); 1680 } else { 1681 if (authority != null) { 1682 result.append("//"); //$NON-NLS-1$ 1683 if (host == null) { 1684 result.append(authority); 1685 } else { 1686 if (userinfo != null) { 1687 result.append(userinfo + "@"); //$NON-NLS-1$ 1688 } 1689 result.append(host.toLowerCase()); 1690 if (port != -1) { 1691 result.append(":" + port); //$NON-NLS-1$ 1692 } 1693 } 1694 } 1695 1696 if (path != null) { 1697 result.append(path); 1698 } 1699 1700 if (query != null) { 1701 result.append('?'); 1702 result.append(query); 1703 } 1704 } 1705 1706 if (fragment != null) { 1707 result.append('#'); 1708 result.append(fragment); 1709 } 1710 1711 return convertHexToLowerCase(result.toString()); 1712 } 1713 1714 /** 1715 * Converts this URI instance to a URL. 1716 * 1717 * @return the created URL representing the same resource as this URI. 1718 * @throws MalformedURLException 1719 * if an error occurs while creating the URL or no protocol 1720 * handler could be found. 1721 */ 1722 public URL toURL() throws MalformedURLException { 1723 if (!absolute) { 1724 throw new IllegalArgumentException(Msg.getString("K0312") + ": " //$NON-NLS-1$//$NON-NLS-2$ 1725 + toString()); 1726 } 1727 return new URL(toString()); 1728 } 1729 1730 private void readObject(ObjectInputStream in) throws IOException, 1731 ClassNotFoundException { 1732 in.defaultReadObject(); 1733 try { 1734 new Helper().parseURI(string, false); 1735 } catch (URISyntaxException e) { 1736 throw new IOException(e.toString()); 1737 } 1738 } 1739 1740 private void writeObject(ObjectOutputStream out) throws IOException, 1741 ClassNotFoundException { 1742 // call toString() to ensure the value of string field is calculated 1743 toString(); 1744 out.defaultWriteObject(); 1745 } 1746} 1747