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: 587072 $ $Date: 2007-10-22 08:38:45 -0400 (Mon, 22 Oct 2007) $
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            ProviderInfo info = (ProviderInfo) providersByClassLoader.get(cl);
501            if (info == null) {
502                info = loadProviders(cl);
503            }
504            return info;
505        }
506    
507        private Map getAddressMap() {
508            ClassLoader cl = getClassLoader();
509            Map addressMap = (Map)addressMapsByClassLoader.get(cl);
510            if (addressMap == null) {
511                addressMap = loadAddressMap(cl);
512            }
513            return addressMap;
514        }
515    
516    
517        /**
518         * Resolve a class loader used to resolve context resources.  The
519         * class loader used is either a current thread context class
520         * loader (if set), the class loader used to load an authenticator
521         * we've been initialized with, or the class loader used to load
522         * this class instance (which may be a subclass of Session).
523         *
524         * @return The class loader used to load resources.
525         */
526        private ClassLoader getClassLoader() {
527            ClassLoader cl = Thread.currentThread().getContextClassLoader();
528            if (cl == null) {
529                if (authenticator != null) {
530                    cl = authenticator.getClass().getClassLoader();
531                }
532                else {
533                    cl = this.getClass().getClassLoader();
534                }
535            }
536            return cl;
537        }
538    
539        private ProviderInfo loadProviders(ClassLoader cl) {
540            // we create a merged map from reading all of the potential address map entries.  The locations
541            // searched are:
542            //   1.   java.home/lib/javamail.address.map
543            //   2. META-INF/javamail.address.map
544            //   3. META-INF/javamail.default.address.map
545            //
546            ProviderInfo info = new ProviderInfo();
547    
548            // make sure this is added to the global map.
549            providersByClassLoader.put(cl, info);
550    
551    
552            // NOTE:  Unlike the addressMap, we process these in the defined order.  The loading routine
553            // will not overwrite entries if they already exist in the map.
554    
555            try {
556                File file = new File(System.getProperty("java.home"), "lib/javamail.providers");
557                InputStream is = new FileInputStream(file);
558                try {
559                    loadProviders(info, is);
560                    if (debug) {
561                        writeDebug("Loaded lib/javamail.providers from " + file.toString());
562                    }
563                } finally{
564                    is.close();
565                }
566            } catch (SecurityException e) {
567                // ignore
568            } catch (IOException e) {
569                // ignore
570            }
571    
572            try {
573                Enumeration e = cl.getResources("META-INF/javamail.providers");
574                while (e.hasMoreElements()) {
575                    URL url = (URL) e.nextElement();
576                    if (debug) {
577                        writeDebug("Loading META-INF/javamail.providers from " + url.toString());
578                    }
579                    InputStream is = url.openStream();
580                    try {
581                        loadProviders(info, is);
582                    } finally{
583                        is.close();
584                    }
585                }
586            } catch (SecurityException e) {
587                // ignore
588            } catch (IOException e) {
589                // ignore
590            }
591    
592            try {
593                Enumeration e = cl.getResources("META-INF/javamail.default.providers");
594                while (e.hasMoreElements()) {
595                    URL url = (URL) e.nextElement();
596                    if (debug) {
597                        writeDebug("Loading javamail.default.providers from " + url.toString());
598                    }
599    
600                    InputStream is = url.openStream();
601                    try {
602                        loadProviders(info, is);
603                    } finally{
604                        is.close();
605                    }
606                }
607            } catch (SecurityException e) {
608                // ignore
609            } catch (IOException e) {
610                // ignore
611            }
612    
613            return info;
614        }
615    
616        private void loadProviders(ProviderInfo info, InputStream is) throws IOException {
617            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
618            String line;
619            while ((line = reader.readLine()) != null) {
620                // Lines beginning with "#" are just comments.  
621                if (line.startsWith("#")) {
622                    continue; 
623                }
624                
625                StringTokenizer tok = new StringTokenizer(line, ";");
626                String protocol = null;
627                Provider.Type type = null;
628                String className = null;
629                String vendor = null;
630                String version = null;
631                while (tok.hasMoreTokens()) {
632                    String property = tok.nextToken();
633                    int index = property.indexOf('=');
634                    if (index == -1) {
635                        continue;
636                    }
637                    String key = property.substring(0, index).trim().toLowerCase();
638                    String value = property.substring(index+1).trim();
639                    if (protocol == null && "protocol".equals(key)) {
640                        protocol = value;
641                    } else if (type == null && "type".equals(key)) {
642                        if ("store".equals(value)) {
643                            type = Provider.Type.STORE;
644                        } else if ("transport".equals(value)) {
645                            type = Provider.Type.TRANSPORT;
646                        }
647                    } else if (className == null && "class".equals(key)) {
648                        className = value;
649                    } else if ("vendor".equals(key)) {
650                        vendor = value;
651                    } else if ("version".equals(key)) {
652                        version = value;
653                    }
654                }
655                if (protocol == null || type == null || className == null) {
656                    //todo should we log a warning?
657                    continue;
658                }
659    
660                if (debug) {
661                    writeDebug("DEBUG: loading new provider protocol=" + protocol + ", className=" + className + ", vendor=" + vendor + ", version=" + version);
662                }
663                Provider provider = new Provider(type, protocol, className, vendor, version);
664                // add to the info list.
665                info.addProvider(provider);
666            }
667        }
668    
669        /**
670         * Load up an address map associated with a using class loader
671         * instance.
672         *
673         * @param cl     The class loader used to resolve the address map.
674         *
675         * @return A map containing the entries associated with this classloader
676         *         instance.
677         */
678        private static Map loadAddressMap(ClassLoader cl) {
679            // we create a merged map from reading all of the potential address map entries.  The locations
680            // searched are:
681            //   1.   java.home/lib/javamail.address.map
682            //   2. META-INF/javamail.address.map
683            //   3. META-INF/javamail.default.address.map
684            //
685            // if all of the above searches fail, we just set up some "default" defaults.
686    
687            // the format of the address.map file is defined as a property file.  We can cheat and
688            // just use Properties.load() to read in the files.
689            Properties addressMap = new Properties();
690    
691            // add this to the tracking map.
692            addressMapsByClassLoader.put(cl, addressMap);
693    
694            // NOTE:  We are reading these resources in reverse order of what's cited above.  This allows
695            // user defined entries to overwrite default entries if there are similarly named items.
696    
697            try {
698                Enumeration e = cl.getResources("META-INF/javamail.default.address.map");
699                while (e.hasMoreElements()) {
700                    URL url = (URL) e.nextElement();
701                    InputStream is = url.openStream();
702                    try {
703                        // load as a property file
704                        addressMap.load(is);
705                    } finally{
706                        is.close();
707                    }
708                }
709            } catch (SecurityException e) {
710                // ignore
711            } catch (IOException e) {
712                // ignore
713            }
714    
715    
716            try {
717                Enumeration e = cl.getResources("META-INF/javamail.address.map");
718                while (e.hasMoreElements()) {
719                    URL url = (URL) e.nextElement();
720                    InputStream is = url.openStream();
721                    try {
722                        // load as a property file
723                        addressMap.load(is);
724                    } finally{
725                        is.close();
726                    }
727                }
728            } catch (SecurityException e) {
729                // ignore
730            } catch (IOException e) {
731                // ignore
732            }
733    
734    
735            try {
736                File file = new File(System.getProperty("java.home"), "lib/javamail.address.map");
737                InputStream is = new FileInputStream(file);
738                try {
739                    // load as a property file
740                    addressMap.load(is);
741                } finally{
742                    is.close();
743                }
744            } catch (SecurityException e) {
745                // ignore
746            } catch (IOException e) {
747                // ignore
748            }
749    
750            try {
751                Enumeration e = cl.getResources("META-INF/javamail.address.map");
752                while (e.hasMoreElements()) {
753                    URL url = (URL) e.nextElement();
754                    InputStream is = url.openStream();
755                    try {
756                        // load as a property file
757                        addressMap.load(is);
758                    } finally{
759                        is.close();
760                    }
761                }
762            } catch (SecurityException e) {
763                // ignore
764            } catch (IOException e) {
765                // ignore
766            }
767    
768    
769            // if unable to load anything, at least create the MimeMessage-smtp protocol mapping.
770            if (addressMap.isEmpty()) {
771                addressMap.put("rfc822", "smtp");
772            }
773    
774            return addressMap;
775        }
776    
777        /**
778         * Private convenience routine for debug output.
779         *
780         * @param msg    The message to write out to the debug stream.
781         */
782        private void writeDebug(String msg) {
783            debugOut.println(msg);
784        }
785    
786    
787        private static class ProviderInfo {
788            private final Map byClassName = new HashMap();
789            private final Map byProtocol = new HashMap();
790            private final List all = new ArrayList();
791    
792            public void addProvider(Provider provider) {
793                String className = provider.getClassName();
794    
795                if (!byClassName.containsKey(className)) {
796                    byClassName.put(className, provider);
797                }
798    
799                String protocol = provider.getProtocol();
800                if (!byProtocol.containsKey(protocol)) {
801                    byProtocol.put(protocol, provider);
802                }
803                all.add(provider);
804            }
805        }
806    }