Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.sql/share/classes/java/sql/DriverManager.java
41153 views
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
}
746
747