1 /*
2  * Copyright (c) 2002, 2013, 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      */
JMXServiceURL(String serviceURL)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      */
JMXServiceURL(String protocol, String host, int port)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      */
JMXServiceURL(String protocol, String host, int port, String urlPath)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";
readObject(ObjectInputStream inputStream)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 = (int)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 
validate(String proto, String h, int p, String url)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 
validate()410     private void validate() throws MalformedURLException {
411         validate(this.protocol, this.host, this.port, this.urlPath);
412     }
413 
validateHost(String h, int port)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      */
getProtocol()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      */
getHost()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      */
getPort()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      */
getURLPath()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      */
toString()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      */
equals(Object obj)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 
hashCode()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      */
isNumericIPv6Address(String s)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
indexOf(String s, char c, int fromIndex)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 
indexOfFirstNotInSet(String s, BitSet set, int fromIndex)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 }
734