1 /*
   2  * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 
  27 package javax.management.remote;
  28 
  29 
  30 import com.sun.jmx.remote.util.ClassLogger;
  31 import com.sun.jmx.remote.util.EnvHelp;
  32 import java.io.IOException;
  33 import java.io.InvalidObjectException;
  34 import java.io.ObjectInputStream;
  35 
  36 import java.io.Serializable;
  37 import java.net.InetAddress;
  38 import java.net.MalformedURLException;
  39 import java.net.UnknownHostException;
  40 import java.util.BitSet;
  41 import java.util.Locale;
  42 import java.util.StringTokenizer;
  43 
  44 /**
  45  * <p>The address of a JMX API connector server.  Instances of this class
  46  * are immutable.</p>
  47  *
  48  * <p>The address is an <em>Abstract Service URL</em> for SLP, as
  49  * defined in RFC 2609 and amended by RFC 3111.  It must look like
  50  * this:</p>
  51  *
  52  * <blockquote>
  53  *
  54  * <code>service:jmx:<em>protocol</em>:<em>sap</em></code>
  55  *
  56  * </blockquote>
  57  *
  58  * <p>Here, <code><em>protocol</em></code> is the transport
  59  * protocol to be used to connect to the connector server.  It is
  60  * a string of one or more ASCII characters, each of which is a
  61  * letter, a digit, or one of the characters <code>+</code> or
  62  * <code>-</code>.  The first character must be a letter.
  63  * Uppercase letters are converted into lowercase ones.</p>
  64  *
  65  * <p><code><em>sap</em></code> is the address at which the connector
  66  * server is found.  This address uses a subset of the syntax defined
  67  * by RFC 2609 for IP-based protocols.  It is a subset because the
  68  * <code>user@host</code> syntax is not supported.</p>
  69  *
  70  * <p>The other syntaxes defined by RFC 2609 are not currently
  71  * supported by this class.</p>
  72  *
  73  * <p>The supported syntax is:</p>
  74  *
  75  * <blockquote>
  76  *
  77  * <code>//<em>[host[</em>:<em>port]][url-path]</em></code>
  78  *
  79  * </blockquote>
  80  *
  81  * <p>Square brackets <code>[]</code> indicate optional parts of
  82  * the address.  Not all protocols will recognize all optional
  83  * parts.</p>
  84  *
  85  * <p>The <code><em>host</em></code> is a host name, an IPv4 numeric
  86  * host address, or an IPv6 numeric address enclosed in square
  87  * brackets.</p>
  88  *
  89  * <p>The <code><em>port</em></code> is a decimal port number.  0
  90  * means a default or anonymous port, depending on the protocol.</p>
  91  *
  92  * <p>The <code><em>host</em></code> and <code><em>port</em></code>
  93  * can be omitted.  The <code><em>port</em></code> cannot be supplied
  94  * without a <code><em>host</em></code>.</p>
  95  *
  96  * <p>The <code><em>url-path</em></code>, if any, begins with a slash
  97  * (<code>/</code>) or a semicolon (<code>;</code>) and continues to
  98  * the end of the address.  It can contain attributes using the
  99  * semicolon syntax specified in RFC 2609.  Those attributes are not
 100  * parsed by this class and incorrect attribute syntax is not
 101  * detected.</p>
 102  *
 103  * <p>Although it is legal according to RFC 2609 to have a
 104  * <code><em>url-path</em></code> that begins with a semicolon, not
 105  * all implementations of SLP allow it, so it is recommended to avoid
 106  * that syntax.</p>
 107  *
 108  * <p>Case is not significant in the initial
 109  * <code>service:jmx:<em>protocol</em></code> string or in the host
 110  * part of the address.  Depending on the protocol, case can be
 111  * significant in the <code><em>url-path</em></code>.</p>
 112  *
 113  * @see <a
 114  * href="http://www.ietf.org/rfc/rfc2609.txt">RFC 2609,
 115  * "Service Templates and <code>Service:</code> Schemes"</a>
 116  * @see <a
 117  * href="http://www.ietf.org/rfc/rfc3111.txt">RFC 3111,
 118  * "Service Location Protocol Modifications for IPv6"</a>
 119  *
 120  * @since 1.5
 121  */
 122 public class JMXServiceURL implements Serializable {
 123 
 124     private static final long serialVersionUID = 8173364409860779292L;
 125 
 126     /**
 127      * <p>Constructs a <code>JMXServiceURL</code> by parsing a Service URL
 128      * string.</p>
 129      *
 130      * @param serviceURL the URL string to be parsed.
 131      *
 132      * @exception NullPointerException if <code>serviceURL</code> is
 133      * null.
 134      *
 135      * @exception MalformedURLException if <code>serviceURL</code>
 136      * does not conform to the syntax for an Abstract Service URL or
 137      * if it is not a valid name for a JMX Remote API service.  A
 138      * <code>JMXServiceURL</code> must begin with the string
 139      * <code>"service:jmx:"</code> (case-insensitive).  It must not
 140      * contain any characters that are not printable ASCII characters.
 141      */
 142     public JMXServiceURL(String serviceURL) throws MalformedURLException {
 143         final int serviceURLLength = serviceURL.length();
 144 
 145         /* Check that there are no non-ASCII characters in the URL,
 146            following RFC 2609.  */
 147         for (int i = 0; i < serviceURLLength; i++) {
 148             char c = serviceURL.charAt(i);
 149             if (c < 32 || c >= 127) {
 150                 throw new MalformedURLException("Service URL contains " +
 151                                                 "non-ASCII character 0x" +
 152                                                 Integer.toHexString(c));
 153             }
 154         }
 155 
 156         // Parse the required prefix
 157         final String requiredPrefix = "service:jmx:";
 158         final int requiredPrefixLength = requiredPrefix.length();
 159         if (!serviceURL.regionMatches(true, // ignore case
 160                                       0,    // serviceURL offset
 161                                       requiredPrefix,
 162                                       0,    // requiredPrefix offset
 163                                       requiredPrefixLength)) {
 164             throw new MalformedURLException("Service URL must start with " +
 165                                             requiredPrefix);
 166         }
 167 
 168         // Parse the protocol name
 169         final int protoStart = requiredPrefixLength;
 170         final int protoEnd = indexOf(serviceURL, ':', protoStart);
 171         this.protocol =
 172             serviceURL.substring(protoStart, protoEnd).toLowerCase(Locale.ENGLISH);
 173 
 174         if (!serviceURL.regionMatches(protoEnd, "://", 0, 3)) {
 175             throw new MalformedURLException("Missing \"://\" after " +
 176                                             "protocol name");
 177         }
 178 
 179         // Parse the host name
 180         final int hostStart = protoEnd + 3;
 181         final int hostEnd;
 182         if (hostStart < serviceURLLength
 183             && serviceURL.charAt(hostStart) == '[') {
 184             hostEnd = serviceURL.indexOf(']', hostStart) + 1;
 185             if (hostEnd == 0)
 186                 throw new MalformedURLException("Bad host name: [ without ]");
 187             this.host = serviceURL.substring(hostStart + 1, hostEnd - 1);
 188             if (!isNumericIPv6Address(this.host)) {
 189                 throw new MalformedURLException("Address inside [...] must " +
 190                                                 "be numeric IPv6 address");
 191             }
 192         } else {
 193             hostEnd =
 194                 indexOfFirstNotInSet(serviceURL, hostNameBitSet, hostStart);
 195             this.host = serviceURL.substring(hostStart, hostEnd);
 196         }
 197 
 198         // Parse the port number
 199         final int portEnd;
 200         if (hostEnd < serviceURLLength && serviceURL.charAt(hostEnd) == ':') {
 201             if (this.host.length() == 0) {
 202                 throw new MalformedURLException("Cannot give port number " +
 203                                                 "without host name");
 204             }
 205             final int portStart = hostEnd + 1;
 206             portEnd =
 207                 indexOfFirstNotInSet(serviceURL, numericBitSet, portStart);
 208             final String portString = serviceURL.substring(portStart, portEnd);
 209             try {
 210                 this.port = Integer.parseInt(portString);
 211             } catch (NumberFormatException e) {
 212                 throw new MalformedURLException("Bad port number: \"" +
 213                                                 portString + "\": " + e);
 214             }
 215         } else {
 216             portEnd = hostEnd;
 217             this.port = 0;
 218         }
 219 
 220         // Parse the URL path
 221         final int urlPathStart = portEnd;
 222         if (urlPathStart < serviceURLLength)
 223             this.urlPath = serviceURL.substring(urlPathStart);
 224         else
 225             this.urlPath = "";
 226 
 227         validate();
 228     }
 229 
 230     /**
 231      * <p>Constructs a <code>JMXServiceURL</code> with the given protocol,
 232      * host, and port.  This constructor is equivalent to
 233      * {@link #JMXServiceURL(String, String, int, String)
 234      * JMXServiceURL(protocol, host, port, null)}.</p>
 235      *
 236      * @param protocol the protocol part of the URL.  If null, defaults
 237      * to <code>jmxmp</code>.
 238      *
 239      * @param host the host part of the URL.  If null, defaults to the
 240      * local host name, as determined by
 241      * <code>InetAddress.getLocalHost().getHostName()</code>.  If it
 242      * is a numeric IPv6 address, it can optionally be enclosed in
 243      * square brackets <code>[]</code>.
 244      *
 245      * @param port the port part of the URL.
 246      *
 247      * @exception MalformedURLException if one of the parts is
 248      * syntactically incorrect, or if <code>host</code> is null and it
 249      * is not possible to find the local host name, or if
 250      * <code>port</code> is negative.
 251      */
 252     public JMXServiceURL(String protocol, String host, int port)
 253             throws MalformedURLException {
 254         this(protocol, host, port, null);
 255     }
 256 
 257     /**
 258      * <p>Constructs a <code>JMXServiceURL</code> with the given parts.
 259      *
 260      * @param protocol the protocol part of the URL.  If null, defaults
 261      * to <code>jmxmp</code>.
 262      *
 263      * @param host the host part of the URL.  If null, defaults to the
 264      * local host name, as determined by
 265      * <code>InetAddress.getLocalHost().getHostName()</code>.  If it
 266      * is a numeric IPv6 address, it can optionally be enclosed in
 267      * square brackets <code>[]</code>.
 268      *
 269      * @param port the port part of the URL.
 270      *
 271      * @param urlPath the URL path part of the URL.  If null, defaults to
 272      * the empty string.
 273      *
 274      * @exception MalformedURLException if one of the parts is
 275      * syntactically incorrect, or if <code>host</code> is null and it
 276      * is not possible to find the local host name, or if
 277      * <code>port</code> is negative.
 278      */
 279     public JMXServiceURL(String protocol, String host, int port,
 280                          String urlPath)
 281             throws MalformedURLException {
 282         if (protocol == null)
 283             protocol = "jmxmp";
 284 
 285         if (host == null) {
 286             InetAddress local;
 287             try {
 288                 local = InetAddress.getLocalHost();
 289             } catch (UnknownHostException e) {
 290                 throw new MalformedURLException("Local host name unknown: " +
 291                                                 e);
 292             }
 293 
 294             host = local.getHostName();
 295 
 296             /* We might have a hostname that violates DNS naming
 297                rules, for example that contains an `_'.  While we
 298                could be strict and throw an exception, this is rather
 299                user-hostile.  Instead we use its numerical IP address.
 300                We can only reasonably do this for the host==null case.
 301                If we're given an explicit host name that is illegal we
 302                have to reject it.  (Bug 5057532.)  */
 303             try {
 304                 validateHost(host, port);
 305             } catch (MalformedURLException e) {
 306                 if (logger.fineOn()) {
 307                     logger.fine("JMXServiceURL",
 308                                 "Replacing illegal local host name " +
 309                                 host + " with numeric IP address " +
 310                                 "(see RFC 1034)", e);
 311                 }
 312                 host = local.getHostAddress();
 313                 /* Use the numeric address, which could be either IPv4
 314                    or IPv6.  validateHost will accept either.  */
 315             }
 316         }
 317 
 318         if (host.startsWith("[")) {
 319             if (!host.endsWith("]")) {
 320                 throw new MalformedURLException("Host starts with [ but " +
 321                                                 "does not end with ]");
 322             }
 323             host = host.substring(1, host.length() - 1);
 324             if (!isNumericIPv6Address(host)) {
 325                 throw new MalformedURLException("Address inside [...] must " +
 326                                                 "be numeric IPv6 address");
 327             }
 328             if (host.startsWith("["))
 329                 throw new MalformedURLException("More than one [[...]]");
 330         }
 331 
 332         this.protocol = protocol.toLowerCase(Locale.ENGLISH);
 333         this.host = host;
 334         this.port = port;
 335 
 336         if (urlPath == null)
 337             urlPath = "";
 338         this.urlPath = urlPath;
 339 
 340         validate();
 341     }
 342 
 343     private static final String INVALID_INSTANCE_MSG =
 344             "Trying to deserialize an invalid instance of JMXServiceURL";
 345     private void readObject(ObjectInputStream  inputStream) throws IOException, ClassNotFoundException {
 346         ObjectInputStream.GetField gf = inputStream.readFields();
 347         String h = (String)gf.get("host", null);
 348         int p = gf.get("port", -1);
 349         String proto = (String)gf.get("protocol", null);
 350         String url = (String)gf.get("urlPath", null);
 351 
 352         if (proto == null || url == null || h == null) {
 353             StringBuilder sb = new StringBuilder(INVALID_INSTANCE_MSG).append('[');
 354             boolean empty = true;
 355             if (proto == null) {
 356                 sb.append("protocol=null");
 357                 empty = false;
 358             }
 359             if (h == null) {
 360                 sb.append(empty ? "" : ",").append("host=null");
 361                 empty = false;
 362             }
 363             if (url == null) {
 364                 sb.append(empty ? "" : ",").append("urlPath=null");
 365             }
 366             sb.append(']');
 367             throw new InvalidObjectException(sb.toString());
 368         }
 369 
 370         if (h.contains("[") || h.contains("]")) {
 371             throw new InvalidObjectException("Invalid host name: " + h);
 372         }
 373 
 374         try {
 375             validate(proto, h, p, url);
 376             this.protocol = proto;
 377             this.host = h;
 378             this.port = p;
 379             this.urlPath = url;
 380         } catch (MalformedURLException e) {
 381             throw new InvalidObjectException(INVALID_INSTANCE_MSG + ": " +
 382                                              e.getMessage());
 383         }
 384 
 385     }
 386 
 387     private void validate(String proto, String h, int p, String url)
 388         throws MalformedURLException {
 389         // Check protocol
 390         final int protoEnd = indexOfFirstNotInSet(proto, protocolBitSet, 0);
 391         if (protoEnd == 0 || protoEnd < proto.length()
 392             || !alphaBitSet.get(proto.charAt(0))) {
 393             throw new MalformedURLException("Missing or invalid protocol " +
 394                                             "name: \"" + proto + "\"");
 395         }
 396 
 397         // Check host
 398         validateHost(h, p);
 399 
 400         // Check port
 401         if (p < 0)
 402             throw new MalformedURLException("Bad port: " + p);
 403 
 404         // Check URL path
 405         if (url.length() > 0) {
 406             if (!url.startsWith("/") && !url.startsWith(";"))
 407                 throw new MalformedURLException("Bad URL path: " + url);
 408         }
 409     }
 410 
 411     private void validate() throws MalformedURLException {
 412         validate(this.protocol, this.host, this.port, this.urlPath);
 413     }
 414 
 415     private static void validateHost(String h, int port)
 416             throws MalformedURLException {
 417 
 418         if (h.length() == 0) {
 419             if (port != 0) {
 420                 throw new MalformedURLException("Cannot give port number " +
 421                                                 "without host name");
 422             }
 423             return;
 424         }
 425 
 426         if (isNumericIPv6Address(h)) {
 427             /* We assume J2SE >= 1.4 here.  Otherwise you can't
 428                use the address anyway.  We can't call
 429                InetAddress.getByName without checking for a
 430                numeric IPv6 address, because we mustn't try to do
 431                a DNS lookup in case the address is not actually
 432                numeric.  */
 433             try {
 434                 InetAddress.getByName(h);
 435             } catch (Exception e) {
 436                 /* We should really catch UnknownHostException
 437                    here, but a bug in JDK 1.4 causes it to throw
 438                    ArrayIndexOutOfBoundsException, e.g. if the
 439                    string is ":".  */
 440                 MalformedURLException bad =
 441                     new MalformedURLException("Bad IPv6 address: " + h);
 442                 EnvHelp.initCause(bad, e);
 443                 throw bad;
 444             }
 445         } else {
 446             /* Tiny state machine to check valid host name.  This
 447                checks the hostname grammar from RFC 1034 (DNS),
 448                page 11.  A hostname is a dot-separated list of one
 449                or more labels, where each label consists of
 450                letters, numbers, or hyphens.  A label cannot begin
 451                or end with a hyphen.  Empty hostnames are not
 452                allowed.  Note that numeric IPv4 addresses are a
 453                special case of this grammar.
 454 
 455                The state is entirely captured by the last
 456                character seen, with a virtual `.' preceding the
 457                name.  We represent any alphanumeric character by
 458                `a'.
 459 
 460                We need a special hack to check, as required by the
 461                RFC 2609 (SLP) grammar, that the last component of
 462                the hostname begins with a letter.  Respecting the
 463                intent of the RFC, we only do this if there is more
 464                than one component.  If your local hostname begins
 465                with a digit, we don't reject it.  */
 466             final int hostLen = h.length();
 467             char lastc = '.';
 468             boolean sawDot = false;
 469             char componentStart = 0;
 470 
 471             loop:
 472             for (int i = 0; i < hostLen; i++) {
 473                 char c = h.charAt(i);
 474                 boolean isAlphaNumeric = alphaNumericBitSet.get(c);
 475                 if (lastc == '.')
 476                     componentStart = c;
 477                 if (isAlphaNumeric)
 478                     lastc = 'a';
 479                 else if (c == '-') {
 480                     if (lastc == '.')
 481                         break; // will throw exception
 482                     lastc = '-';
 483                 } else if (c == '.') {
 484                     sawDot = true;
 485                     if (lastc != 'a')
 486                         break; // will throw exception
 487                     lastc = '.';
 488                 } else {
 489                     lastc = '.'; // will throw exception
 490                     break;
 491                 }
 492             }
 493 
 494             try {
 495                 if (lastc != 'a')
 496                     throw randomException;
 497                 if (sawDot && !alphaBitSet.get(componentStart)) {
 498                     /* Must be a numeric IPv4 address.  In addition to
 499                        the explicitly-thrown exceptions, we can get
 500                        NoSuchElementException from the calls to
 501                        tok.nextToken and NumberFormatException from
 502                        the call to Integer.parseInt.  Using exceptions
 503                        for control flow this way is a bit evil but it
 504                        does simplify things enormously.  */
 505                     StringTokenizer tok = new StringTokenizer(h, ".", true);
 506                     for (int i = 0; i < 4; i++) {
 507                         String ns = tok.nextToken();
 508                         int n = Integer.parseInt(ns);
 509                         if (n < 0 || n > 255)
 510                             throw randomException;
 511                         if (i < 3 && !tok.nextToken().equals("."))
 512                             throw randomException;
 513                     }
 514                     if (tok.hasMoreTokens())
 515                         throw randomException;
 516                 }
 517             } catch (Exception e) {
 518                 throw new MalformedURLException("Bad host: \"" + h + "\"");
 519             }
 520         }
 521     }
 522 
 523     private static final Exception randomException = new Exception();
 524 
 525 
 526     /**
 527      * <p>The protocol part of the Service URL.
 528      *
 529      * @return the protocol part of the Service URL.  This is never null.
 530      */
 531     public String getProtocol() {
 532         return protocol;
 533     }
 534 
 535     /**
 536      * <p>The host part of the Service URL.  If the Service URL was
 537      * constructed with the constructor that takes a URL string
 538      * parameter, the result is the substring specifying the host in
 539      * that URL.  If the Service URL was constructed with a
 540      * constructor that takes a separate host parameter, the result is
 541      * the string that was specified.  If that string was null, the
 542      * result is
 543      * <code>InetAddress.getLocalHost().getHostName()</code>.</p>
 544      *
 545      * <p>In either case, if the host was specified using the
 546      * <code>[...]</code> syntax for numeric IPv6 addresses, the
 547      * square brackets are not included in the return value here.</p>
 548      *
 549      * @return the host part of the Service URL.  This is never null.
 550      */
 551     public String getHost() {
 552         return host;
 553     }
 554 
 555     /**
 556      * <p>The port of the Service URL.  If no port was
 557      * specified, the returned value is 0.</p>
 558      *
 559      * @return the port of the Service URL, or 0 if none.
 560      */
 561     public int getPort() {
 562         return port;
 563     }
 564 
 565     /**
 566      * <p>The URL Path part of the Service URL.  This is an empty
 567      * string, or a string beginning with a slash (<code>/</code>), or
 568      * a string beginning with a semicolon (<code>;</code>).
 569      *
 570      * @return the URL Path part of the Service URL.  This is never
 571      * null.
 572      */
 573     public String getURLPath() {
 574         return urlPath;
 575     }
 576 
 577     /**
 578      * <p>The string representation of this Service URL.  If the value
 579      * returned by this method is supplied to the
 580      * <code>JMXServiceURL</code> constructor, the resultant object is
 581      * equal to this one.</p>
 582      *
 583      * <p>The <code><em>host</em></code> part of the returned string
 584      * is the value returned by {@link #getHost()}.  If that value
 585      * specifies a numeric IPv6 address, it is surrounded by square
 586      * brackets <code>[]</code>.</p>
 587      *
 588      * <p>The <code><em>port</em></code> part of the returned string
 589      * is the value returned by {@link #getPort()} in its shortest
 590      * decimal form.  If the value is zero, it is omitted.</p>
 591      *
 592      * @return the string representation of this Service URL.
 593      */
 594     public String toString() {
 595         /* We don't bother synchronizing the access to toString.  At worst,
 596            n threads will independently compute and store the same value.  */
 597         if (toString != null)
 598             return toString;
 599         StringBuilder buf = new StringBuilder("service:jmx:");
 600         buf.append(getProtocol()).append("://");
 601         final String getHost = getHost();
 602         if (isNumericIPv6Address(getHost))
 603             buf.append('[').append(getHost).append(']');
 604         else
 605             buf.append(getHost);
 606         final int getPort = getPort();
 607         if (getPort != 0)
 608             buf.append(':').append(getPort);
 609         buf.append(getURLPath());
 610         toString = buf.toString();
 611         return toString;
 612     }
 613 
 614     /**
 615      * <p>Indicates whether some other object is equal to this one.
 616      * This method returns true if and only if <code>obj</code> is an
 617      * instance of <code>JMXServiceURL</code> whose {@link
 618      * #getProtocol()}, {@link #getHost()}, {@link #getPort()}, and
 619      * {@link #getURLPath()} methods return the same values as for
 620      * this object.  The values for {@link #getProtocol()} and {@link
 621      * #getHost()} can differ in case without affecting equality.
 622      *
 623      * @param obj the reference object with which to compare.
 624      *
 625      * @return <code>true</code> if this object is the same as the
 626      * <code>obj</code> argument; <code>false</code> otherwise.
 627      */
 628     public boolean equals(Object obj) {
 629         if (!(obj instanceof JMXServiceURL))
 630             return false;
 631         JMXServiceURL u = (JMXServiceURL) obj;
 632         return
 633             (u.getProtocol().equalsIgnoreCase(getProtocol()) &&
 634              u.getHost().equalsIgnoreCase(getHost()) &&
 635              u.getPort() == getPort() &&
 636              u.getURLPath().equals(getURLPath()));
 637     }
 638 
 639     public int hashCode() {
 640         return toString().hashCode();
 641     }
 642 
 643     /* True if this string, assumed to be a valid argument to
 644      * InetAddress.getByName, is a numeric IPv6 address.
 645      */
 646     private static boolean isNumericIPv6Address(String s) {
 647         // address contains colon if and only if it's a numeric IPv6 address
 648         return (s.indexOf(':') >= 0);
 649     }
 650 
 651     // like String.indexOf but returns string length not -1 if not present
 652     private static int indexOf(String s, char c, int fromIndex) {
 653         int index = s.indexOf(c, fromIndex);
 654         if (index < 0)
 655             return s.length();
 656         else
 657             return index;
 658     }
 659 
 660     private static int indexOfFirstNotInSet(String s, BitSet set,
 661                                             int fromIndex) {
 662         final int slen = s.length();
 663         int i = fromIndex;
 664         while (true) {
 665             if (i >= slen)
 666                 break;
 667             char c = s.charAt(i);
 668             if (c >= 128)
 669                 break; // not ASCII
 670             if (!set.get(c))
 671                 break;
 672             i++;
 673         }
 674         return i;
 675     }
 676 
 677     private final static BitSet alphaBitSet = new BitSet(128);
 678     private final static BitSet numericBitSet = new BitSet(128);
 679     private final static BitSet alphaNumericBitSet = new BitSet(128);
 680     private final static BitSet protocolBitSet = new BitSet(128);
 681     private final static BitSet hostNameBitSet = new BitSet(128);
 682     static {
 683         /* J2SE 1.4 adds lots of handy methods to BitSet that would
 684            allow us to simplify here, e.g. by not writing loops, but
 685            we want to work on J2SE 1.3 too.  */
 686 
 687         for (char c = '0'; c <= '9'; c++)
 688             numericBitSet.set(c);
 689 
 690         for (char c = 'A'; c <= 'Z'; c++)
 691             alphaBitSet.set(c);
 692         for (char c = 'a'; c <= 'z'; c++)
 693             alphaBitSet.set(c);
 694 
 695         alphaNumericBitSet.or(alphaBitSet);
 696         alphaNumericBitSet.or(numericBitSet);
 697 
 698         protocolBitSet.or(alphaNumericBitSet);
 699         protocolBitSet.set('+');
 700         protocolBitSet.set('-');
 701 
 702         hostNameBitSet.or(alphaNumericBitSet);
 703         hostNameBitSet.set('-');
 704         hostNameBitSet.set('.');
 705     }
 706 
 707     /**
 708      * The value returned by {@link #getProtocol()}.
 709      */
 710     private String protocol;
 711 
 712     /**
 713      * The value returned by {@link #getHost()}.
 714      */
 715     private String host;
 716 
 717     /**
 718      * The value returned by {@link #getPort()}.
 719      */
 720     private int port;
 721 
 722     /**
 723      * The value returned by {@link #getURLPath()}.
 724      */
 725     private String urlPath;
 726 
 727     /**
 728      * Cached result of {@link #toString()}.
 729      */
 730     private transient String toString;
 731 
 732     private static final ClassLogger logger =
 733         new ClassLogger("javax.management.remote.misc", "JMXServiceURL");
 734 }