001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package javax.mail;
021    
022    import java.io.BufferedReader;
023    import java.io.File;
024    import java.io.FileInputStream;
025    import java.io.IOException;
026    import java.io.InputStream;
027    import java.io.InputStreamReader;
028    import java.io.PrintStream;
029    import java.lang.reflect.Constructor;
030    import java.lang.reflect.InvocationTargetException;
031    import java.net.InetAddress;
032    import java.net.URL;
033    import java.util.ArrayList;
034    import java.util.Enumeration;
035    import java.util.HashMap;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.Properties;
039    import java.util.StringTokenizer;
040    import java.util.WeakHashMap;
041    
042    
043    /**
044     * OK, so we have a final class in the API with a heck of a lot of implementation required...
045     * let's try and figure out what it is meant to do.
046     * <p/>
047     * It is supposed to collect together properties and defaults so that they can be
048     * shared by multiple applications on a desktop; with process isolation and no
049     * real concept of shared memory, this seems challenging. These properties and
050     * defaults rely on system properties, making management in a app server harder,
051     * and on resources loaded from "mail.jar" which may lead to skew between                    
052     * differnet independent implementations of this API.
053     *
054     * @version $Rev: 702537 $ $Date: 2008-10-07 12:39:34 -0400 (Tue, 07 Oct 2008) $
055     */
056    public final class Session {
057        private static final Class[] PARAM_TYPES = {Session.class, URLName.class};
058        private static final WeakHashMap addressMapsByClassLoader = new WeakHashMap();
059        private static Session DEFAULT_SESSION;
060    
061        private Map passwordAuthentications = new HashMap();
062    
063        private final Properties properties;
064        private final Authenticator authenticator;
065        private boolean debug;
066        private PrintStream debugOut = System.out;
067    
068        private static final WeakHashMap providersByClassLoader = new WeakHashMap();
069    
070        /**
071         * No public constrcutor allowed.
072         */
073        private Session(Properties properties, Authenticator authenticator) {
074            this.properties = properties;
075            this.authenticator = authenticator;
076            debug = Boolean.valueOf(properties.getProperty("mail.debug")).booleanValue();
077        }
078    
079        /**
080         * Create a new session initialized with the supplied properties which uses the supplied authenticator.
081         * Clients should ensure the properties listed in Appendix A of the JavaMail specification are
082         * set as the defaults are unlikey to work in most scenarios; particular attention should be given
083         * to:
084         * <ul>
085         * <li>mail.store.protocol</li>
086         * <li>mail.transport.protocol</li>
087         * <li>mail.host</li>
088         * <li>mail.user</li>
089         * <li>mail.from</li>
090         * </ul>
091         *
092         * @param properties    the session properties
093         * @param authenticator an authenticator for callbacks to the user
094         * @return a new session
095         */
096        public static Session getInstance(Properties properties, Authenticator authenticator) {
097            return new Session(new Properties(properties), authenticator);
098        }
099    
100        /**
101         * Create a new session initialized with the supplied properties with no authenticator.
102         *
103         * @param properties the session properties
104         * @return a new session
105         * @see #getInstance(java.util.Properties, Authenticator)
106         */
107        public static Session getInstance(Properties properties) {
108            return getInstance(properties, null);
109        }
110    
111        /**
112         * Get the "default" instance assuming no authenticator is required.
113         *
114         * @param properties the session properties
115         * @return if "default" session
116         * @throws SecurityException if the does not have permission to access the default session
117         */
118        public synchronized static Session getDefaultInstance(Properties properties) {
119            return getDefaultInstance(properties, null);
120        }
121    
122        /**
123         * Get the "default" session.
124         * If there is not current "default", a new Session is created and installed as the default.
125         *
126         * @param properties
127         * @param authenticator
128         * @return if "default" session
129         * @throws SecurityException if the does not have permission to access the default session
130         */
131        public synchronized static Session getDefaultInstance(Properties properties, Authenticator authenticator) {
132            if (DEFAULT_SESSION == null) {
133                DEFAULT_SESSION = getInstance(properties, authenticator);
134            } else {
135                if (authenticator != DEFAULT_SESSION.authenticator) {
136                    if (authenticator == null || DEFAULT_SESSION.authenticator == null || authenticator.getClass().getClassLoader() != DEFAULT_SESSION.authenticator.getClass().getClassLoader()) {
137                        throw new SecurityException();
138                    }
139                }
140                // todo we should check with the SecurityManager here as well
141            }
142            return DEFAULT_SESSION;
143        }
144    
145        /**
146         * Enable debugging for this session.
147         * Debugging can also be enabled by setting the "mail.debug" property to true when
148         * the session is being created.
149         *
150         * @param debug the debug setting
151         */
152        public void setDebug(boolean debug) {
153            this.debug = debug;
154        }
155    
156        /**
157         * Get the debug setting for this session.
158         *
159         * @return the debug setting
160         */
161        public boolean getDebug() {
162            return debug;
163        }
164    
165        /**
166         * Set the output stream where debug information should be sent.
167         * If set to null, System.out will be used.
168         *
169         * @param out the stream to write debug information to
170         */
171        public void setDebugOut(PrintStream out) {
172            debugOut = out == null ? System.out : out;
173        }
174    
175        /**
176         * Return the debug output stream.
177         *
178         * @return the debug output stream
179         */
180        public PrintStream getDebugOut() {
181            return debugOut;
182        }
183    
184        /**
185         * Return the list of providers available to this application.
186         * This method searches for providers that are defined in the javamail.providers
187         * and javamail.default.providers resources available through the current context
188         * classloader, or if that is not available, the classloader that loaded this class.
189         * <p/>
190         * As searching for providers is potentially expensive, this implementation maintains
191         * a WeakHashMap of providers indexed by ClassLoader.
192         *
193         * @return an array of providers
194         */
195        public Provider[] getProviders() {
196            ProviderInfo info = getProviderInfo();
197            return (Provider[]) info.all.toArray(new Provider[info.all.size()]);
198        }
199    
200        /**
201         * Return the provider for a specific protocol.
202         * This implementation initially looks in the Session properties for an property with the name
203         * "mail.<protocol>.class"; if found it attempts to create an instance of the class named in that
204         * property throwing a NoSuchProviderException if the class cannot be loaded.
205         * If this property is not found, it searches the providers returned by {@link #getProviders()}
206         * for a entry for the specified protocol.
207         *
208         * @param protocol the protocol to get a provider for
209         * @return a provider for that protocol
210         * @throws NoSuchProviderException
211         */
212        public Provider getProvider(String protocol) throws NoSuchProviderException {
213            ProviderInfo info = getProviderInfo();
214            Provider provider = null;
215            String providerName = properties.getProperty("mail." + protocol + ".class");
216            if (providerName != null) {
217                provider = (Provider) info.byClassName.get(providerName);
218                if (debug) {
219                    writeDebug("DEBUG: new provider loaded: " + provider.toString());
220                }
221            }
222    
223            // if not able to locate this by class name, just grab a registered protocol.
224            if (provider == null) {
225                provider = (Provider) info.byProtocol.get(protocol);
226            }
227    
228            if (provider == null) {
229                throw new NoSuchProviderException("Unable to locate provider for protocol: " + protocol);
230            }
231            if (debug) {
232                writeDebug("DEBUG: getProvider() returning provider " + provider.toString());
233            }
234            return provider;
235        }
236    
237        /**
238         * Make the supplied Provider the default for its protocol.
239         *
240         * @param provider the new default Provider
241         * @throws NoSuchProviderException
242         */
243        public void setProvider(Provider provider) throws NoSuchProviderException {
244            ProviderInfo info = getProviderInfo();
245            info.byProtocol.put(provider.getProtocol(), provider);
246        }
247    
248        /**
249         * Return a Store for the default protocol defined by the mail.store.protocol property.
250         *
251         * @return the store for the default protocol
252         * @throws NoSuchProviderException
253         */
254        public Store getStore() throws NoSuchProviderException {
255            String protocol = properties.getProperty("mail.store.protocol");
256            if (protocol == null) {
257                throw new NoSuchProviderException("mail.store.protocol property is not set");
258            }
259            return getStore(protocol);
260        }
261    
262        /**
263         * Return a Store for the specified protocol.
264         *
265         * @param protocol the protocol to get a Store for
266         * @return a Store
267         * @throws NoSuchProviderException if no provider is defined for the specified protocol
268         */
269        public Store getStore(String protocol) throws NoSuchProviderException {
270            Provider provider = getProvider(protocol);
271            return getStore(provider);
272        }
273    
274        /**
275         * Return a Store for the protocol specified in the given URL
276         *
277         * @param url the URL of the Store
278         * @return a Store
279         * @throws NoSuchProviderException if no provider is defined for the specified protocol
280         */
281        public Store getStore(URLName url) throws NoSuchProviderException {
282            return (Store) getService(getProvider(url.getProtocol()), url);
283        }
284    
285        /**
286         * Return the Store specified by the given provider.
287         *
288         * @param provider the provider to create from
289         * @return a Store
290         * @throws NoSuchProviderException if there was a problem creating the Store
291         */
292        public Store getStore(Provider provider) throws NoSuchProviderException {
293            if (Provider.Type.STORE != provider.getType()) {
294                throw new NoSuchProviderException("Not a Store Provider: " + provider);
295            }
296            return (Store) getService(provider, null);
297        }
298    
299        /**
300         * Return a closed folder for the supplied URLName, or null if it cannot be obtained.
301         * <p/>
302         * The scheme portion of the URL is used to locate the Provider and create the Store;
303         * the returned Store is then used to obtain the folder.
304         *
305         * @param name the location of the folder
306         * @return the requested folder, or null if it is unavailable
307         * @throws NoSuchProviderException if there is no provider
308         * @throws MessagingException      if there was a problem accessing the Store
309         */
310        public Folder getFolder(URLName name) throws MessagingException {
311            Store store = getStore(name);
312            return store.getFolder(name);
313        }
314    
315        /**
316         * Return a Transport for the default protocol specified by the
317         * <code>mail.transport.protocol</code> property.
318         *
319         * @return a Transport
320         * @throws NoSuchProviderException
321         */
322        public Transport getTransport() throws NoSuchProviderException {
323            String protocol = properties.getProperty("mail.transport.protocol");
324            if (protocol == null) {
325                throw new NoSuchProviderException("mail.transport.protocol property is not set");
326            }
327            return getTransport(protocol);
328        }
329    
330        /**
331         * Return a Transport for the specified protocol.
332         *
333         * @param protocol the protocol to use
334         * @return a Transport
335         * @throws NoSuchProviderException
336         */
337        public Transport getTransport(String protocol) throws NoSuchProviderException {
338            Provider provider = getProvider(protocol);
339            return getTransport(provider);
340        }
341    
342        /**
343         * Return a transport for the protocol specified in the URL.
344         *
345         * @param name the URL whose scheme specifies the protocol
346         * @return a Transport
347         * @throws NoSuchProviderException
348         */
349        public Transport getTransport(URLName name) throws NoSuchProviderException {
350            return (Transport) getService(getProvider(name.getProtocol()), name);
351        }
352    
353        /**
354         * Return a transport for the protocol associated with the type of this address.
355         *
356         * @param address the address we are trying to deliver to
357         * @return a Transport
358         * @throws NoSuchProviderException
359         */
360        public Transport getTransport(Address address) throws NoSuchProviderException {
361            String type = address.getType();
362            // load the address map from the resource files.
363            Map addressMap = getAddressMap();
364            String protocolName = (String)addressMap.get(type);
365            if (protocolName == null) {
366                throw new NoSuchProviderException("No provider for address type " + type);
367            }
368            return getTransport(protocolName);
369        }
370    
371        /**
372         * Return the Transport specified by a Provider
373         *
374         * @param provider the defining Provider
375         * @return a Transport
376         * @throws NoSuchProviderException
377         */
378        public Transport getTransport(Provider provider) throws NoSuchProviderException {
379            return (Transport) getService(provider, null);
380        }
381    
382        /**
383         * Set the password authentication associated with a URL.
384         *
385         * @param name          the url
386         * @param authenticator the authenticator
387         */
388        public void setPasswordAuthentication(URLName name, PasswordAuthentication authenticator) {
389            if (authenticator == null) {
390                passwordAuthentications.remove(name);
391            } else {
392                passwordAuthentications.put(name, authenticator);
393            }
394        }
395    
396        /**
397         * Get the password authentication associated with a URL
398         *
399         * @param name the URL
400         * @return any authenticator for that url, or null if none
401         */
402        public PasswordAuthentication getPasswordAuthentication(URLName name) {
403            return (PasswordAuthentication) passwordAuthentications.get(name);
404        }
405    
406        /**
407         * Call back to the application supplied authenticator to get the needed username add password.
408         *
409         * @param host            the host we are trying to connect to, may be null
410         * @param port            the port on that host
411         * @param protocol        the protocol trying to be used
412         * @param prompt          a String to show as part of the prompt, may be null
413         * @param defaultUserName the default username, may be null
414         * @return the authentication information collected by the authenticator; may be null
415         */
416        public PasswordAuthentication requestPasswordAuthentication(InetAddress host, int port, String protocol, String prompt, String defaultUserName) {
417            if (authenticator == null) {
418                return null;
419            }
420            return authenticator.authenticate(host, port, protocol, prompt, defaultUserName);
421        }
422    
423        /**
424         * Return the properties object for this Session; this is a live collection.
425         *
426         * @return the properties for the Session
427         */
428        public Properties getProperties() {
429            return properties;
430        }
431    
432        /**
433         * Return the specified property.
434         *
435         * @param property the property to get
436         * @return its value, or null if not present
437         */
438        public String getProperty(String property) {
439            return getProperties().getProperty(property);
440        }
441    
442    
443        /**
444         * Add a provider to the Session managed provider list.
445         *
446         * @param provider The new provider to add.
447         */
448        public synchronized void addProvider(Provider provider) {
449            ProviderInfo info = getProviderInfo();
450            info.addProvider(provider);
451        }
452    
453    
454    
455        /**
456         * Add a mapping between an address type and a protocol used
457         * to process that address type.
458         *
459         * @param addressType
460         *                 The address type identifier.
461         * @param protocol The protocol name mapping.
462         */
463        public void setProtocolForAddress(String addressType, String protocol) {
464            Map addressMap = getAddressMap();
465    
466            // no protocol specified is a removal
467            if (protocol == null) {
468                addressMap.remove(addressType);
469            }
470            else {
471                addressMap.put(addressType, protocol);
472            }
473        }
474    
475    
476        private Service getService(Provider provider, URLName name) throws NoSuchProviderException {
477            try {
478                if (name == null) {
479                    name = new URLName(provider.getProtocol(), null, -1, null, null, null); 
480               }
481                ClassLoader cl = getClassLoader();
482                Class clazz = cl.loadClass(provider.getClassName());
483                Constructor ctr = clazz.getConstructor(PARAM_TYPES);
484                return (Service) ctr.newInstance(new Object[]{this, name});
485            } catch (ClassNotFoundException e) {
486                throw (NoSuchProviderException) new NoSuchProviderException("Unable to load class for provider: " + provider).initCause(e);
487            } catch (NoSuchMethodException e) {
488                throw (NoSuchProviderException) new NoSuchProviderException("Provider class does not have a constructor(Session, URLName): " + provider).initCause(e);
489            } catch (InstantiationException e) {
490                throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
491            } catch (IllegalAccessException e) {
492                throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
493            } catch (InvocationTargetException e) {
494                throw (NoSuchProviderException) new NoSuchProviderException("Exception from constructor of provider class: " + provider).initCause(e.getCause());
495            }
496        }
497    
498        private ProviderInfo getProviderInfo() {
499            ClassLoader cl = getClassLoader();
500            synchronized (providersByClassLoader) {
501                ProviderInfo info = (ProviderInfo) providersByClassLoader.get(cl);
502                if (info == null) {
503                    info = loadProviders(cl);
504                }
505                return info;
506            }
507        }
508    
509        private Map getAddressMap() {
510            ClassLoader cl = getClassLoader();
511            Map addressMap = (Map)addressMapsByClassLoader.get(cl);
512            if (addressMap == null) {
513                addressMap = loadAddressMap(cl);
514            }
515            return addressMap;
516        }
517    
518    
519        /**
520         * Resolve a class loader used to resolve context resources.  The
521         * class loader used is either a current thread context class
522         * loader (if set), the class loader used to load an authenticator
523         * we've been initialized with, or the class loader used to load
524         * this class instance (which may be a subclass of Session).
525         *
526         * @return The class loader used to load resources.
527         */
528        private ClassLoader getClassLoader() {
529            ClassLoader cl = Thread.currentThread().getContextClassLoader();
530            if (cl == null) {
531                if (authenticator != null) {
532                    cl = authenticator.getClass().getClassLoader();
533                }
534                else {
535                    cl = this.getClass().getClassLoader();
536                }
537            }
538            return cl;
539        }
540    
541        private ProviderInfo loadProviders(ClassLoader cl) {
542            // we create a merged map from reading all of the potential address map entries.  The locations
543            // searched are:
544            //   1.   java.home/lib/javamail.address.map
545            //   2. META-INF/javamail.address.map
546            //   3. META-INF/javamail.default.address.map
547            //
548            ProviderInfo info = new ProviderInfo();
549    
550            // NOTE:  Unlike the addressMap, we process these in the defined order.  The loading routine
551            // will not overwrite entries if they already exist in the map.
552    
553            try {
554                File file = new File(System.getProperty("java.home"), "lib/javamail.providers");
555                InputStream is = new FileInputStream(file);
556                try {
557                    loadProviders(info, is);
558                    if (debug) {
559                        writeDebug("Loaded lib/javamail.providers from " + file.toString());
560                    }
561                } finally{
562                    is.close();
563                }
564            } catch (SecurityException e) {
565                // ignore
566            } catch (IOException e) {
567                // ignore
568            }
569    
570            try {
571                Enumeration e = cl.getResources("META-INF/javamail.providers");
572                while (e.hasMoreElements()) {
573                    URL url = (URL) e.nextElement();
574                    if (debug) {
575                        writeDebug("Loading META-INF/javamail.providers from " + url.toString());
576                    }
577                    InputStream is = url.openStream();
578                    try {
579                        loadProviders(info, is);
580                    } finally{
581                        is.close();
582                    }
583                }
584            } catch (SecurityException e) {
585                // ignore
586            } catch (IOException e) {
587                // ignore
588            }
589    
590            try {
591                Enumeration e = cl.getResources("META-INF/javamail.default.providers");
592                while (e.hasMoreElements()) {
593                    URL url = (URL) e.nextElement();
594                    if (debug) {
595                        writeDebug("Loading javamail.default.providers from " + url.toString());
596                    }
597    
598                    InputStream is = url.openStream();
599                    try {
600                        loadProviders(info, is);
601                    } finally{
602                        is.close();
603                    }
604                }
605            } catch (SecurityException e) {
606                // ignore
607            } catch (IOException e) {
608                // ignore
609            }
610            
611            // make sure this is added to the global map.
612            providersByClassLoader.put(cl, info); 
613    
614            return info;
615        }
616    
617        private void loadProviders(ProviderInfo info, InputStream is) throws IOException {
618            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
619            String line;
620            while ((line = reader.readLine()) != null) {
621                // Lines beginning with "#" are just comments.  
622                if (line.startsWith("#")) {
623                    continue; 
624                }
625                
626                StringTokenizer tok = new StringTokenizer(line, ";");
627                String protocol = null;
628                Provider.Type type = null;
629                String className = null;
630                String vendor = null;
631                String version = null;
632                while (tok.hasMoreTokens()) {
633                    String property = tok.nextToken();
634                    int index = property.indexOf('=');
635                    if (index == -1) {
636                        continue;
637                    }
638                    String key = property.substring(0, index).trim().toLowerCase();
639                    String value = property.substring(index+1).trim();
640                    if (protocol == null && "protocol".equals(key)) {
641                        protocol = value;
642                    } else if (type == null && "type".equals(key)) {
643                        if ("store".equals(value)) {
644                            type = Provider.Type.STORE;
645                        } else if ("transport".equals(value)) {
646                            type = Provider.Type.TRANSPORT;
647                        }
648                    } else if (className == null && "class".equals(key)) {
649                        className = value;
650                    } else if ("vendor".equals(key)) {
651                        vendor = value;
652                    } else if ("version".equals(key)) {
653                        version = value;
654                    }
655                }
656                if (protocol == null || type == null || className == null) {
657                    //todo should we log a warning?
658                    continue;
659                }
660    
661                if (debug) {
662                    writeDebug("DEBUG: loading new provider protocol=" + protocol + ", className=" + className + ", vendor=" + vendor + ", version=" + version);
663                }
664                Provider provider = new Provider(type, protocol, className, vendor, version);
665                // add to the info list.
666                info.addProvider(provider);
667            }
668        }
669    
670        /**
671         * Load up an address map associated with a using class loader
672         * instance.
673         *
674         * @param cl     The class loader used to resolve the address map.
675         *
676         * @return A map containing the entries associated with this classloader
677         *         instance.
678         */
679        private static Map loadAddressMap(ClassLoader cl) {
680            // we create a merged map from reading all of the potential address map entries.  The locations
681            // searched are:
682            //   1.   java.home/lib/javamail.address.map
683            //   2. META-INF/javamail.address.map
684            //   3. META-INF/javamail.default.address.map
685            //
686            // if all of the above searches fail, we just set up some "default" defaults.
687    
688            // the format of the address.map file is defined as a property file.  We can cheat and
689            // just use Properties.load() to read in the files.
690            Properties addressMap = new Properties();
691    
692            // add this to the tracking map.
693            addressMapsByClassLoader.put(cl, addressMap);
694    
695            // NOTE:  We are reading these resources in reverse order of what's cited above.  This allows
696            // user defined entries to overwrite default entries if there are similarly named items.
697    
698            try {
699                Enumeration e = cl.getResources("META-INF/javamail.default.address.map");
700                while (e.hasMoreElements()) {
701                    URL url = (URL) e.nextElement();
702                    InputStream is = url.openStream();
703                    try {
704                        // load as a property file
705                        addressMap.load(is);
706                    } finally{
707                        is.close();
708                    }
709                }
710            } catch (SecurityException e) {
711                // ignore
712            } catch (IOException e) {
713                // ignore
714            }
715    
716    
717            try {
718                Enumeration e = cl.getResources("META-INF/javamail.address.map");
719                while (e.hasMoreElements()) {
720                    URL url = (URL) e.nextElement();
721                    InputStream is = url.openStream();
722                    try {
723                        // load as a property file
724                        addressMap.load(is);
725                    } finally{
726                        is.close();
727                    }
728                }
729            } catch (SecurityException e) {
730                // ignore
731            } catch (IOException e) {
732                // ignore
733            }
734    
735    
736            try {
737                File file = new File(System.getProperty("java.home"), "lib/javamail.address.map");
738                InputStream is = new FileInputStream(file);
739                try {
740                    // load as a property file
741                    addressMap.load(is);
742                } finally{
743                    is.close();
744                }
745            } catch (SecurityException e) {
746                // ignore
747            } catch (IOException e) {
748                // ignore
749            }
750    
751            try {
752                Enumeration e = cl.getResources("META-INF/javamail.address.map");
753                while (e.hasMoreElements()) {
754                    URL url = (URL) e.nextElement();
755                    InputStream is = url.openStream();
756                    try {
757                        // load as a property file
758                        addressMap.load(is);
759                    } finally{
760                        is.close();
761                    }
762                }
763            } catch (SecurityException e) {
764                // ignore
765            } catch (IOException e) {
766                // ignore
767            }
768    
769    
770            // if unable to load anything, at least create the MimeMessage-smtp protocol mapping.
771            if (addressMap.isEmpty()) {
772                addressMap.put("rfc822", "smtp");
773            }
774    
775            return addressMap;
776        }
777    
778        /**
779         * Private convenience routine for debug output.
780         *
781         * @param msg    The message to write out to the debug stream.
782         */
783        private void writeDebug(String msg) {
784            debugOut.println(msg);
785        }
786    
787    
788        private static class ProviderInfo {
789            private final Map byClassName = new HashMap();
790            private final Map byProtocol = new HashMap();
791            private final List all = new ArrayList();
792    
793            public void addProvider(Provider provider) {
794                String className = provider.getClassName();
795    
796                if (!byClassName.containsKey(className)) {
797                    byClassName.put(className, provider);
798                }
799    
800                String protocol = provider.getProtocol();
801                if (!byProtocol.containsKey(protocol)) {
802                    byProtocol.put(protocol, provider);
803                }
804                all.add(provider);
805            }
806        }
807    }