1 /*
  2  * Copyright (c) 1996, 2021, 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 package java.sql;
 27 
 28 import java.util.ArrayList;
 29 import java.util.Collections;
 30 import java.util.Enumeration;
 31 import java.util.Iterator;
 32 import java.util.List;
 33 import java.util.ServiceLoader;
 34 import java.security.AccessController;
 35 import java.security.PrivilegedAction;
 36 import java.util.concurrent.CopyOnWriteArrayList;
 37 import java.util.stream.Stream;
 38 
 39 import jdk.internal.reflect.CallerSensitive;
 40 import jdk.internal.reflect.Reflection;
 41 
 42 
 43 /**
 44  * The basic service for managing a set of JDBC drivers.
 45  * <p>
 46  * <strong>NOTE:</strong> The {@link javax.sql.DataSource} interface, provides
 47  * another way to connect to a data source.
 48  * The use of a {@code DataSource} object is the preferred means of
 49  * connecting to a data source.
 50  * <P>
 51  * As part of its initialization, the {@code DriverManager} class will
 52  * attempt to load available JDBC drivers by using:
 53  * <ul>
 54  * <li>The {@code jdbc.drivers} system property which contains a
 55  * colon separated list of fully qualified class names of JDBC drivers. Each
 56  * driver is loaded using the {@linkplain ClassLoader#getSystemClassLoader
 57  * system class loader}:
 58  * <ul>
 59  * <li>{@code jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.taste.ourDriver}
 60  * </ul>
 61  *
 62  * <li>Service providers of the {@code java.sql.Driver} class, that are loaded
 63  * via the {@linkplain ServiceLoader#load service-provider loading} mechanism.
 64  *</ul>
 65  *
 66  * @implNote
 67  * {@code DriverManager} initialization is done lazily and looks up service
 68  * providers using the thread context class loader.  The drivers loaded and
 69  * available to an application will depend on the thread context class loader of
 70  * the thread that triggers driver initialization by {@code DriverManager}.
 71  *
 72  * <P>When the method {@code getConnection} is called,
 73  * the {@code DriverManager} will attempt to
 74  * locate a suitable driver from amongst those loaded at
 75  * initialization and those loaded explicitly using the same class loader
 76  * as the current application.
 77  *
 78  * @see Driver
 79  * @see Connection
 80  * @since 1.1
 81  */
 82 public class DriverManager {
 83 
 84 
 85     // List of registered JDBC drivers
 86     private static final CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
 87     private static volatile int loginTimeout = 0;
 88     private static volatile java.io.PrintWriter logWriter = null;
 89     private static volatile java.io.PrintStream logStream = null;
 90     // Used in println() to synchronize logWriter
 91     private static final Object logSync = new Object();
 92     // Used in ensureDriversInitialized() to synchronize driversInitialized
 93     private static final Object lockForInitDrivers = new Object();
 94     private static volatile boolean driversInitialized;
 95     private static final String JDBC_DRIVERS_PROPERTY = "jdbc.drivers";
 96 
 97     /* Prevent the DriverManager class from being instantiated. */
 98     private DriverManager(){}
 99 
100     /**
101      * The {@code SQLPermission} constant that allows the
102      * setting of the logging stream.
103      * @since 1.3
104      */
105     static final SQLPermission SET_LOG_PERMISSION =
106         new SQLPermission("setLog");
107 
108     /**
109      * The {@code SQLPermission} constant that allows the
110      * un-register a registered JDBC driver.
111      * @since 1.8
112      */
113     static final SQLPermission DEREGISTER_DRIVER_PERMISSION =
114         new SQLPermission("deregisterDriver");
115 
116     //--------------------------JDBC 2.0-----------------------------
117 
118     /**
119      * Retrieves the log writer.
120      *
121      * The {@code getLogWriter} and {@code setLogWriter}
122      * methods should be used instead
123      * of the {@code get/setlogStream} methods, which are deprecated.
124      * @return a {@code java.io.PrintWriter} object
125      * @see #setLogWriter
126      * @since 1.2
127      */
128     public static java.io.PrintWriter getLogWriter() {
129             return logWriter;
130     }
131 
132     /**
133      * Sets the logging/tracing {@code PrintWriter} object
134      * that is used by the {@code DriverManager} and all drivers.
135      *<P>
136      * If a security manager exists, its {@code checkPermission}
137      * method is first called with a {@code SQLPermission("setLog")}
138      * permission to check that the caller is allowed to call {@code setLogWriter}.
139      *
140      * @param out the new logging/tracing {@code PrintStream} object;
141      *      {@code null} to disable logging and tracing
142      * @throws SecurityException if a security manager exists and its
143      * {@code checkPermission} method denies permission to set the log writer.
144      * @see SecurityManager#checkPermission
145      * @see #getLogWriter
146      * @since 1.2
147      */
148     public static void setLogWriter(java.io.PrintWriter out) {
149 
150         @SuppressWarnings("removal")
151         SecurityManager sec = System.getSecurityManager();
152         if (sec != null) {
153             sec.checkPermission(SET_LOG_PERMISSION);
154         }
155             logStream = null;
156             logWriter = out;
157     }
158 
159 
160     //---------------------------------------------------------------
161 
162     /**
163      * Attempts to establish a connection to the given database URL.
164      * The {@code DriverManager} attempts to select an appropriate driver from
165      * the set of registered JDBC drivers.
166      *<p>
167      * <B>Note:</B> If a property is specified as part of the {@code url} and
168      * is also specified in the {@code Properties} object, it is
169      * implementation-defined as to which value will take precedence.
170      * For maximum portability, an application should only specify a
171      * property once.
172      *
173      * @param url a database url of the form
174      * <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
175      * @param info a list of arbitrary string tag/value pairs as
176      * connection arguments; normally at least a "user" and
177      * "password" property should be included
178      * @return a Connection to the URL
179      * @throws SQLException if a database access error occurs or the url is
180      * {@code null}
181      * @throws SQLTimeoutException  when the driver has determined that the
182      * timeout value specified by the {@code setLoginTimeout} method
183      * has been exceeded and has at least tried to cancel the
184      * current database connection attempt
185      */
186     @CallerSensitive
187     public static Connection getConnection(String url,
188         java.util.Properties info) throws SQLException {
189 
190         return (getConnection(url, info, Reflection.getCallerClass()));
191     }
192 
193     /**
194      * Attempts to establish a connection to the given database URL.
195      * The {@code DriverManager} attempts to select an appropriate driver from
196      * the set of registered JDBC drivers.
197      *<p>
198      * <B>Note:</B> If the {@code user} or {@code password} property are
199      * also specified as part of the {@code url}, it is
200      * implementation-defined as to which value will take precedence.
201      * For maximum portability, an application should only specify a
202      * property once.
203      *
204      * @param url a database url of the form
205      * <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
206      * @param user the database user on whose behalf the connection is being
207      *   made
208      * @param password the user's password
209      * @return a connection to the URL
210      * @throws SQLException if a database access error occurs or the url is
211      * {@code null}
212      * @throws SQLTimeoutException  when the driver has determined that the
213      * timeout value specified by the {@code setLoginTimeout} method
214      * has been exceeded and has at least tried to cancel the
215      * current database connection attempt
216      */
217     @CallerSensitive
218     public static Connection getConnection(String url,
219         String user, String password) throws SQLException {
220         java.util.Properties info = new java.util.Properties();
221 
222         if (user != null) {
223             info.put("user", user);
224         }
225         if (password != null) {
226             info.put("password", password);
227         }
228 
229         return (getConnection(url, info, Reflection.getCallerClass()));
230     }
231 
232     /**
233      * Attempts to establish a connection to the given database URL.
234      * The {@code DriverManager} attempts to select an appropriate driver from
235      * the set of registered JDBC drivers.
236      *
237      * @param url a database url of the form
238      *  <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
239      * @return a connection to the URL
240      * @throws SQLException if a database access error occurs or the url is
241      * {@code null}
242      * @throws SQLTimeoutException  when the driver has determined that the
243      * timeout value specified by the {@code setLoginTimeout} method
244      * has been exceeded and has at least tried to cancel the
245      * current database connection attempt
246      */
247     @CallerSensitive
248     public static Connection getConnection(String url)
249         throws SQLException {
250 
251         java.util.Properties info = new java.util.Properties();
252         return (getConnection(url, info, Reflection.getCallerClass()));
253     }
254 
255     /**
256      * Attempts to locate a driver that understands the given URL.
257      * The {@code DriverManager} attempts to select an appropriate driver from
258      * the set of registered JDBC drivers.
259      *
260      * @param url a database URL of the form
261      *     <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
262      * @return a {@code Driver} object representing a driver
263      * that can connect to the given URL
264      * @throws SQLException if a database access error occurs
265      */
266     @CallerSensitive
267     public static Driver getDriver(String url)
268         throws SQLException {
269 
270         println("DriverManager.getDriver(\"" + url + "\")");
271 
272         ensureDriversInitialized();
273 
274         Class<?> callerClass = Reflection.getCallerClass();
275 
276         // Walk through the loaded registeredDrivers attempting to locate someone
277         // who understands the given URL.
278         for (DriverInfo aDriver : registeredDrivers) {
279             // If the caller does not have permission to load the driver then
280             // skip it.
281             if (isDriverAllowed(aDriver.driver, callerClass)) {
282                 try {
283                     if (aDriver.driver.acceptsURL(url)) {
284                         // Success!
285                         println("getDriver returning " + aDriver.driver.getClass().getName());
286                     return (aDriver.driver);
287                     }
288 
289                 } catch(SQLException sqe) {
290                     // Drop through and try the next driver.
291                 }
292             } else {
293                 println("    skipping: " + aDriver.driver.getClass().getName());
294             }
295 
296         }
297 
298         println("getDriver: no suitable driver");
299         throw new SQLException("No suitable driver", "08001");
300     }
301 
302 
303     /**
304      * Registers the given driver with the {@code DriverManager}.
305      * A newly-loaded driver class should call
306      * the method {@code registerDriver} to make itself
307      * known to the {@code DriverManager}. If the driver is currently
308      * registered, no action is taken.
309      *
310      * @param driver the new JDBC Driver that is to be registered with the
311      *               {@code DriverManager}
312      * @throws SQLException if a database access error occurs
313      * @throws NullPointerException if {@code driver} is null
314      */
315     public static void registerDriver(java.sql.Driver driver)
316         throws SQLException {
317 
318         registerDriver(driver, null);
319     }
320 
321     /**
322      * Registers the given driver with the {@code DriverManager}.
323      * A newly-loaded driver class should call
324      * the method {@code registerDriver} to make itself
325      * known to the {@code DriverManager}. If the driver is currently
326      * registered, no action is taken.
327      *
328      * @param driver the new JDBC Driver that is to be registered with the
329      *               {@code DriverManager}
330      * @param da     the {@code DriverAction} implementation to be used when
331      *               {@code DriverManager#deregisterDriver} is called
332      * @throws SQLException if a database access error occurs
333      * @throws NullPointerException if {@code driver} is null
334      * @since 1.8
335      */
336     public static void registerDriver(java.sql.Driver driver,
337             DriverAction da)
338         throws SQLException {
339 
340         /* Register the driver if it has not already been added to our list */
341         if (driver != null) {
342             registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
343         } else {
344             // This is for compatibility with the original DriverManager
345             throw new NullPointerException();
346         }
347 
348         println("registerDriver: " + driver);
349 
350     }
351 
352     /**
353      * Removes the specified driver from the {@code DriverManager}'s list of
354      * registered drivers.
355      * <p>
356      * If a {@code null} value is specified for the driver to be removed, then no
357      * action is taken.
358      * <p>
359      * If a security manager exists, its {@code checkPermission}
360      * method is first called with a {@code SQLPermission("deregisterDriver")}
361      * permission to check that the caller is allowed to deregister a JDBC Driver.
362      * <p>
363      * If the specified driver is not found in the list of registered drivers,
364      * then no action is taken.  If the driver was found, it will be removed
365      * from the list of registered drivers.
366      * <p>
367      * If a {@code DriverAction} instance was specified when the JDBC driver was
368      * registered, its deregister method will be called
369      * prior to the driver being removed from the list of registered drivers.
370      *
371      * @param driver the JDBC Driver to remove
372      * @throws SQLException if a database access error occurs
373      * @throws SecurityException if a security manager exists and its
374      * {@code checkPermission} method denies permission to deregister a driver.
375      *
376      * @see SecurityManager#checkPermission
377      */
378     @CallerSensitive
379     public static void deregisterDriver(Driver driver) throws SQLException {
380         if (driver == null) {
381             return;
382         }
383 
384         @SuppressWarnings("removal")
385         SecurityManager sec = System.getSecurityManager();
386         if (sec != null) {
387             sec.checkPermission(DEREGISTER_DRIVER_PERMISSION);
388         }
389 
390         println("DriverManager.deregisterDriver: " + driver);
391 
392         DriverInfo aDriver = new DriverInfo(driver, null);
393         synchronized (lockForInitDrivers) {
394             if (registeredDrivers.contains(aDriver)) {
395                 if (isDriverAllowed(driver, Reflection.getCallerClass())) {
396                     DriverInfo di = registeredDrivers.get(registeredDrivers.indexOf(aDriver));
397                      // If a DriverAction was specified, Call it to notify the
398                      // driver that it has been deregistered
399                      if (di.action() != null) {
400                          di.action().deregister();
401                      }
402                      registeredDrivers.remove(aDriver);
403                 } else {
404                     // If the caller does not have permission to load the driver then
405                     // throw a SecurityException.
406                     throw new SecurityException();
407                 }
408             } else {
409                 println("    couldn't find driver to unload");
410             }
411         }
412     }
413 
414     /**
415      * Retrieves an Enumeration with all of the currently loaded JDBC drivers
416      * to which the current caller has access.
417      *
418      * <P><B>Note:</B> The classname of a driver can be found using
419      * {@code d.getClass().getName()}
420      *
421      * @return the list of JDBC Drivers loaded by the caller's class loader
422      * @see #drivers()
423      */
424     @CallerSensitive
425     public static Enumeration<Driver> getDrivers() {
426         ensureDriversInitialized();
427 
428         return Collections.enumeration(getDrivers(Reflection.getCallerClass()));
429     }
430 
431     /**
432      * Retrieves a Stream with all of the currently loaded JDBC drivers
433      * to which the current caller has access.
434      *
435      * @return the stream of JDBC Drivers loaded by the caller's class loader
436      * @since 9
437      */
438     @CallerSensitive
439     public static Stream<Driver> drivers() {
440         ensureDriversInitialized();
441 
442         return getDrivers(Reflection.getCallerClass()).stream();
443     }
444 
445     private static List<Driver> getDrivers(Class<?> callerClass) {
446         List<Driver> result = new ArrayList<>();
447         // Walk through the loaded registeredDrivers.
448         for (DriverInfo aDriver : registeredDrivers) {
449             // If the caller does not have permission to load the driver then
450             // skip it.
451             if (isDriverAllowed(aDriver.driver, callerClass)) {
452                 result.add(aDriver.driver);
453             } else {
454                 println("    skipping: " + aDriver.driver.getClass().getName());
455             }
456         }
457         return result;
458     }
459 
460     /**
461      * Sets the maximum time in seconds that a driver will wait
462      * while attempting to connect to a database once the driver has
463      * been identified.
464      *
465      * @param seconds the login time limit in seconds; zero means there is no limit
466      * @see #getLoginTimeout
467      */
468     public static void setLoginTimeout(int seconds) {
469         loginTimeout = seconds;
470     }
471 
472     /**
473      * Gets the maximum time in seconds that a driver can wait
474      * when attempting to log in to a database.
475      *
476      * @return the driver login time limit in seconds
477      * @see #setLoginTimeout
478      */
479     public static int getLoginTimeout() {
480         return (loginTimeout);
481     }
482 
483     /**
484      * Sets the logging/tracing PrintStream that is used
485      * by the {@code DriverManager}
486      * and all drivers.
487      *<P>
488      * If a security manager exists, its {@code checkPermission}
489      * method is first called with a {@code SQLPermission("setLog")}
490      * permission to check that the caller is allowed to call {@code setLogStream}.
491      *
492      * @param out the new logging/tracing PrintStream; to disable, set to {@code null}
493      * @deprecated Use {@code setLogWriter}
494      * @throws SecurityException if a security manager exists and its
495      * {@code checkPermission} method denies permission to set the log stream.
496      * @see SecurityManager#checkPermission
497      * @see #getLogStream
498      */
499     @Deprecated(since="1.2")
500     public static void setLogStream(java.io.PrintStream out) {
501 
502         @SuppressWarnings("removal")
503         SecurityManager sec = System.getSecurityManager();
504         if (sec != null) {
505             sec.checkPermission(SET_LOG_PERMISSION);
506         }
507 
508         logStream = out;
509         if ( out != null )
510             logWriter = new java.io.PrintWriter(out);
511         else
512             logWriter = null;
513     }
514 
515     /**
516      * Retrieves the logging/tracing PrintStream that is used by the {@code DriverManager}
517      * and all drivers.
518      *
519      * @return the logging/tracing PrintStream; if disabled, is {@code null}
520      * @deprecated  Use {@code getLogWriter}
521      * @see #setLogStream
522      */
523     @Deprecated(since="1.2")
524     public static java.io.PrintStream getLogStream() {
525         return logStream;
526     }
527 
528     /**
529      * Prints a message to the current JDBC log stream.
530      *
531      * @param message a log or tracing message
532      */
533     public static void println(String message) {
534         synchronized (logSync) {
535             if (logWriter != null) {
536                 logWriter.println(message);
537 
538                 // automatic flushing is never enabled, so we must do it ourselves
539                 logWriter.flush();
540             }
541         }
542     }
543 
544     //------------------------------------------------------------------------
545 
546     // Indicates whether the class object that would be created if the code calling
547     // DriverManager is accessible.
548     private static boolean isDriverAllowed(Driver driver, Class<?> caller) {
549         ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
550         return isDriverAllowed(driver, callerCL);
551     }
552 
553     private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
554         boolean result = false;
555         if (driver != null) {
556             Class<?> aClass = null;
557             try {
558                 aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
559             } catch (Exception ex) {
560                 result = false;
561             }
562 
563              result = ( aClass == driver.getClass() ) ? true : false;
564         }
565 
566         return result;
567     }
568 
569     /*
570      * Load the initial JDBC drivers by checking the System property
571      * jdbc.drivers and then use the {@code ServiceLoader} mechanism
572      */
573     @SuppressWarnings("removal")
574     private static void ensureDriversInitialized() {
575         if (driversInitialized) {
576             return;
577         }
578 
579         synchronized (lockForInitDrivers) {
580             if (driversInitialized) {
581                 return;
582             }
583             String drivers;
584             try {
585                 drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
586                     public String run() {
587                         return System.getProperty(JDBC_DRIVERS_PROPERTY);
588                     }
589                 });
590             } catch (Exception ex) {
591                 drivers = null;
592             }
593             // If the driver is packaged as a Service Provider, load it.
594             // Get all the drivers through the classloader
595             // exposed as a java.sql.Driver.class service.
596             // ServiceLoader.load() replaces the sun.misc.Providers()
597 
598             AccessController.doPrivileged(new PrivilegedAction<Void>() {
599                 public Void run() {
600 
601                     ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
602                     Iterator<Driver> driversIterator = loadedDrivers.iterator();
603 
604                     /* Load these drivers, so that they can be instantiated.
605                      * It may be the case that the driver class may not be there
606                      * i.e. there may be a packaged driver with the service class
607                      * as implementation of java.sql.Driver but the actual class
608                      * may be missing. In that case a java.util.ServiceConfigurationError
609                      * will be thrown at runtime by the VM trying to locate
610                      * and load the service.
611                      *
612                      * Adding a try catch block to catch those runtime errors
613                      * if driver not available in classpath but it's
614                      * packaged as service and that service is there in classpath.
615                      */
616                     try {
617                         while (driversIterator.hasNext()) {
618                             driversIterator.next();
619                         }
620                     } catch (Throwable t) {
621                         // Do nothing
622                     }
623                     return null;
624                 }
625             });
626 
627             println("DriverManager.initialize: jdbc.drivers = " + drivers);
628 
629             if (drivers != null && !drivers.isEmpty()) {
630                 String[] driversList = drivers.split(":");
631                 println("number of Drivers:" + driversList.length);
632                 for (String aDriver : driversList) {
633                     try {
634                         println("DriverManager.Initialize: loading " + aDriver);
635                         Class.forName(aDriver, true,
636                                 ClassLoader.getSystemClassLoader());
637                     } catch (Exception ex) {
638                         println("DriverManager.Initialize: load failed: " + ex);
639                     }
640                 }
641             }
642 
643             driversInitialized = true;
644             println("JDBC DriverManager initialized");
645         }
646     }
647 
648 
649     //  Worker method called by the public getConnection() methods.
650     private static Connection getConnection(
651         String url, java.util.Properties info, Class<?> caller) throws SQLException {
652         /*
653          * When callerCl is null, we should check the application's
654          * (which is invoking this class indirectly)
655          * classloader, so that the JDBC driver class outside rt.jar
656          * can be loaded from here.
657          */
658         ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
659         if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {
660             callerCL = Thread.currentThread().getContextClassLoader();
661         }
662 
663         if (url == null) {
664             throw new SQLException("The url cannot be null", "08001");
665         }
666 
667         println("DriverManager.getConnection(\"" + url + "\")");
668 
669         ensureDriversInitialized();
670 
671         // Walk through the loaded registeredDrivers attempting to make a connection.
672         // Remember the first exception that gets raised so we can reraise it.
673         SQLException reason = null;
674 
675         for (DriverInfo aDriver : registeredDrivers) {
676             // If the caller does not have permission to load the driver then
677             // skip it.
678             if (isDriverAllowed(aDriver.driver, callerCL)) {
679                 try {
680                     println("    trying " + aDriver.driver.getClass().getName());
681                     Connection con = aDriver.driver.connect(url, info);
682                     if (con != null) {
683                         // Success!
684                         println("getConnection returning " + aDriver.driver.getClass().getName());
685                         return (con);
686                     }
687                 } catch (SQLException ex) {
688                     if (reason == null) {
689                         reason = ex;
690                     }
691                 }
692 
693             } else {
694                 println("    skipping: " + aDriver.driver.getClass().getName());
695             }
696 
697         }
698 
699         // if we got here nobody could connect.
700         if (reason != null)    {
701             println("getConnection failed: " + reason);
702             throw reason;
703         }
704 
705         println("getConnection: no suitable driver found for "+ url);
706         throw new SQLException("No suitable driver found for "+ url, "08001");
707     }
708 
709 
710 }
711 
712 /*
713  * Wrapper class for registered Drivers in order to not expose Driver.equals()
714  * to avoid the capture of the Driver it being compared to as it might not
715  * normally have access.
716  */
717 class DriverInfo {
718 
719     final Driver driver;
720     DriverAction da;
721     DriverInfo(Driver driver, DriverAction action) {
722         this.driver = driver;
723         da = action;
724     }
725 
726     @Override
727     public boolean equals(Object other) {
728         return (other instanceof DriverInfo)
729                 && this.driver == ((DriverInfo) other).driver;
730     }
731 
732     @Override
733     public int hashCode() {
734         return driver.hashCode();
735     }
736 
737     @Override
738     public String toString() {
739         return ("driver[className="  + driver + "]");
740     }
741 
742     DriverAction action() {
743         return da;
744     }
745 }