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