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