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