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 }