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