[c3p0] 02/03: Imported Upstream version 0.9.1.2

Tony Mancill tmancill at moszumanska.debian.org
Fri Nov 27 04:49:08 UTC 2015


This is an automated email from the git hooks/post-receive script.

tmancill pushed a commit to branch master
in repository c3p0.

commit 81475a188ee1476b15b7902799fd3391d0feeb2c
Author: tony mancill <tmancill at debian.org>
Date:   Thu Nov 26 17:07:07 2015 -0800

    Imported Upstream version 0.9.1.2
---
 .classpath                                         |    6 +
 .project                                           |   11 +
 README-SRC                                         |   16 +
 build.properties                                   |   90 +
 build.xml                                          |  772 ++++++
 build.xml.bkup                                     |  728 ++++++
 dbms/oracle-thin/build.properties                  |   10 +
 dbms/oracle-thin/build.xml                         |   68 +
 .../classes/com/mchange/v2/c3p0/dbms/Debug.java    |   42 +
 .../com/mchange/v2/c3p0/dbms/OracleUtils.java      |  122 +
 relproj/build.xml                                  |   26 +
 relproj/debuggen/build.xml                         |   71 +
 .../debuggen/src/app-rsrc/META-INF/manifest.src    |    2 +
 .../src/classes/com/mchange/v1/io/WriterUtils.java |   41 +
 .../classes/com/mchange/v1/lang/BooleanUtils.java  |   40 +
 .../com/mchange/v1/util/ClosableResource.java      |   40 +
 .../classes/com/mchange/v1/util/IteratorUtils.java |  159 ++
 .../src/classes/com/mchange/v1/util/SetUtils.java  |   83 +
 .../com/mchange/v1/util/StringTokenizerUtils.java  |   48 +
 .../src/classes/com/mchange/v1/util/UIterator.java |   42 +
 .../v2/cmdline/BadCommandLineException.java        |   33 +
 .../com/mchange/v2/cmdline/CommandLineUtils.java   |   86 +
 .../mchange/v2/cmdline/MissingSwitchException.java |   38 +
 .../com/mchange/v2/cmdline/ParsedCommandLine.java  |   34 +
 .../mchange/v2/cmdline/ParsedCommandLineImpl.java  |  124 +
 .../cmdline/UnexpectedSwitchArgumentException.java |   43 +
 .../v2/cmdline/UnexpectedSwitchException.java      |   38 +
 .../com/mchange/v2/debug/DebugConstants.java       |   31 +
 .../src/classes/com/mchange/v2/debug/DebugGen.java |  371 +++
 .../com/mchange/v2/io/DirectoryDescentUtils.java   |  130 +
 .../classes/com/mchange/v2/io/FileIterator.java    |   48 +
 relproj/debuggen/version.properties                |    3 +
 src/classes/com/mchange/lang/ByteUtils.java        |  111 +
 .../com/mchange/lang/PotentiallySecondary.java     |   34 +
 .../mchange/lang/PotentiallySecondaryError.java    |   74 +
 .../lang/PotentiallySecondaryException.java        |   91 +
 .../lang/PotentiallySecondaryRuntimeException.java |   74 +
 src/classes/com/mchange/lang/ThrowableUtils.java   |   51 +
 src/classes/com/mchange/util/AssertException.java  |   33 +
 .../com/mchange/v1/db/sql/ConnectionUtils.java     |   75 +
 .../com/mchange/v1/db/sql/ResultSetUtils.java      |   55 +
 .../com/mchange/v1/db/sql/StatementUtils.java      |   55 +
 .../com/mchange/v1/identicator/IdHashKey.java      |   41 +
 .../com/mchange/v1/identicator/IdHashMap.java      |   35 +
 src/classes/com/mchange/v1/identicator/IdMap.java  |  131 ++
 .../com/mchange/v1/identicator/IdWeakHashMap.java  |  252 ++
 .../com/mchange/v1/identicator/Identicator.java    |   34 +
 .../mchange/v1/identicator/StrongIdHashKey.java    |   52 +
 .../com/mchange/v1/identicator/WeakIdHashKey.java  |   85 +
 .../com/mchange/v1/io/InputStreamUtils.java        |  142 ++
 .../com/mchange/v1/io/OutputStreamUtils.java       |   48 +
 .../com/mchange/v1/jvm/InternalNameUtils.java      |  211 ++
 .../com/mchange/v1/jvm/TypeFormatException.java    |   33 +
 .../v1/lang/AmbiguousClassNameException.java       |   34 +
 src/classes/com/mchange/v1/lang/BooleanUtils.java  |   40 +
 src/classes/com/mchange/v1/lang/ClassUtils.java    |  217 ++
 src/classes/com/mchange/v1/lang/GentleThread.java  |   98 +
 src/classes/com/mchange/v1/lang/NullUtils.java     |   43 +
 .../com/mchange/v1/util/AbstractMapEntry.java      |   56 +
 src/classes/com/mchange/v1/util/ArrayUtils.java    |  300 +++
 .../com/mchange/v1/util/ClosableResource.java      |   40 +
 .../com/mchange/v1/util/ClosableResourceUtils.java |   55 +
 src/classes/com/mchange/v1/util/DebugUtils.java    |   38 +
 .../com/mchange/v1/util/SimpleMapEntry.java        |   51 +
 .../com/mchange/v1/util/StringTokenizerUtils.java  |   48 +
 .../com/mchange/v1/util/WrapperIterator.java       |  115 +
 src/classes/com/mchange/v1/xml/DomParseUtils.java  |  179 ++
 .../com/mchange/v2/async/AsynchronousRunner.java   |   56 +
 .../com/mchange/v2/async/CarefulRunnableQueue.java |  235 ++
 src/classes/com/mchange/v2/async/Queuable.java     |   29 +
 .../v2/async/RoundRobinAsynchronousRunner.java     |  154 ++
 .../com/mchange/v2/async/RunnableQueue.java        |   32 +
 .../mchange/v2/async/StrandedTaskReporting.java    |   42 +
 .../v2/async/ThreadPerTaskAsynchronousRunner.java  |  261 +++
 .../v2/async/ThreadPoolAsynchronousRunner.java     |  718 ++++++
 ...readPerTaskAsynchronousRunnerJUnitTestCase.java |  208 ++
 .../ThreadPoolAsynchronousRunnerJUnitTestCase.java |  121 +
 src/classes/com/mchange/v2/beans/BeansUtils.java   |  493 ++++
 src/classes/com/mchange/v2/beans/StateBean.java    |   27 +
 .../com/mchange/v2/beans/StateBeanExporter.java    |   34 +
 .../com/mchange/v2/beans/StateBeanImporter.java    |   29 +
 .../v2/c3p0/AbstractConnectionCustomizer.java      |   51 +
 .../mchange/v2/c3p0/AbstractConnectionTester.java  |   83 +
 .../com/mchange/v2/c3p0/C3P0ProxyConnection.java   |   83 +
 .../com/mchange/v2/c3p0/C3P0ProxyStatement.java    |   84 +
 src/classes/com/mchange/v2/c3p0/C3P0Registry.java  |  339 +++
 .../com/mchange/v2/c3p0/ComboPooledDataSource.java |  684 ++++++
 .../com/mchange/v2/c3p0/ConnectionCustomizer.java  |   89 +
 .../com/mchange/v2/c3p0/ConnectionTester.java      |   65 +
 src/classes/com/mchange/v2/c3p0/DataSources.java   |  368 +++
 .../mchange/v2/c3p0/DriverManagerDataSource.java   |  254 ++
 .../v2/c3p0/DriverManagerDataSourceFactory.java    |  154 ++
 .../mchange/v2/c3p0/FullQueryConnectionTester.java |   32 +
 .../v2/c3p0/JndiRefConnectionPoolDataSource.java   |  311 +++
 .../v2/c3p0/JndiRefForwardingDataSource.java       |  169 ++
 .../com/mchange/v2/c3p0/PoolBackedDataSource.java  |   39 +
 .../v2/c3p0/PoolBackedDataSourceFactory.java       |  670 ++++++
 src/classes/com/mchange/v2/c3p0/PoolConfig.java    |  635 +++++
 .../com/mchange/v2/c3p0/PooledDataSource.java      |  283 +++
 .../com/mchange/v2/c3p0/QueryConnectionTester.java |   32 +
 src/classes/com/mchange/v2/c3p0/SQLWarnings.java   |   48 +
 .../mchange/v2/c3p0/UnifiedConnectionTester.java   |   69 +
 .../v2/c3p0/WrapperConnectionPoolDataSource.java   |  286 +++
 .../com/mchange/v2/c3p0/cfg/C3P0Config.java        |  329 +++
 .../com/mchange/v2/c3p0/cfg/C3P0ConfigFinder.java  |   31 +
 .../com/mchange/v2/c3p0/cfg/C3P0ConfigUtils.java   |  160 ++
 .../mchange/v2/c3p0/cfg/C3P0ConfigXmlUtils.java    |  232 ++
 .../v2/c3p0/cfg/DefaultC3P0ConfigFinder.java       |   88 +
 .../com/mchange/v2/c3p0/cfg/NamedScope.java        |   46 +
 .../c3p0/codegen/BeangenDataSourceGenerator.java   |  230 ++
 .../v2/c3p0/codegen/JdbcProxyGenerator.java        | 1018 ++++++++
 .../mchange/v2/c3p0/filter/FilterDataSource.java   |   73 +
 .../v2/c3p0/impl/AbstractC3P0PooledConnection.java |   35 +
 .../v2/c3p0/impl/AbstractIdentityTokenized.java    |   47 +
 .../v2/c3p0/impl/AbstractPoolBackedDataSource.java |  500 ++++
 .../v2/c3p0/impl/AuthMaskingProperties.java        |   67 +
 .../com/mchange/v2/c3p0/impl/C3P0Defaults.java     |  207 ++
 .../com/mchange/v2/c3p0/impl/C3P0ImplUtils.java    |  381 +++
 .../v2/c3p0/impl/C3P0JavaBeanObjectFactory.java    |   57 +
 .../mchange/v2/c3p0/impl/C3P0PooledConnection.java | 1179 ++++++++++
 .../v2/c3p0/impl/C3P0PooledConnectionPool.java     |  921 ++++++++
 .../c3p0/impl/C3P0PooledConnectionPoolManager.java | 1091 +++++++++
 src/classes/com/mchange/v2/c3p0/impl/DbAuth.java   |   98 +
 .../v2/c3p0/impl/DefaultConnectionTester.java      |  237 ++
 .../v2/c3p0/impl/IdentityTokenResolvable.java      |   60 +
 .../mchange/v2/c3p0/impl/IdentityTokenized.java    |   30 +
 .../impl/IdentityTokenizedCoalesceChecker.java     |   54 +
 .../v2/c3p0/impl/InternalPooledConnection.java     |   34 +
 .../mchange/v2/c3p0/impl/NewPooledConnection.java  |  740 ++++++
 .../impl/NullStatementSetManagedResultSet.java     |   47 +
 .../v2/c3p0/impl/SetManagedDatabaseMetaData.java   |  136 ++
 .../mchange/v2/c3p0/impl/SetManagedResultSet.java  |   60 +
 .../v2/c3p0/impl/SnatchFromSetResultSet.java       |   49 +
 .../v2/c3p0/jboss/C3P0PooledDataSource.java        |  500 ++++
 .../v2/c3p0/jboss/C3P0PooledDataSourceMBean.java   |  184 ++
 .../management/ActiveManagementCoordinator.java    |  151 ++
 .../v2/c3p0/management/C3P0RegistryManager.java    |   77 +
 .../c3p0/management/C3P0RegistryManagerMBean.java  |   46 +
 .../DynamicPooledDataSourceManagerMBean.java       |  619 +++++
 .../v2/c3p0/management/ManagementCoordinator.java  |   35 +
 .../c3p0/management/NullManagementCoordinator.java |   42 +
 .../c3p0/management/PooledDataSourceManager.java   |  126 +
 .../management/PooledDataSourceManagerMBean.java   |   61 +
 .../v2/c3p0/mbean/C3P0PooledDataSource.java        |  433 ++++
 .../v2/c3p0/mbean/C3P0PooledDataSourceMBean.java   |  190 ++
 .../v2/c3p0/stmt/DoubleMaxStatementCache.java      |   73 +
 .../v2/c3p0/stmt/GlobalMaxOnlyStatementCache.java  |   57 +
 .../mchange/v2/c3p0/stmt/GooGooStatementCache.java |  801 +++++++
 .../stmt/MemoryCoalescedStatementCacheKey.java     |  165 ++
 .../stmt/PerConnectionMaxOnlyStatementCache.java   |   57 +
 .../v2/c3p0/stmt/SimpleStatementCacheKey.java      |  155 ++
 .../com/mchange/v2/c3p0/stmt/StatementCache.java   |   49 +
 .../v2/c3p0/stmt/StatementCacheBenchmark.java      |  175 ++
 .../mchange/v2/c3p0/stmt/StatementCacheKey.java    |  180 ++
 .../c3p0/stmt/ValueIdentityStatementCacheKey.java  |  202 ++
 .../mchange/v2/c3p0/subst/C3P0Substitutions.java   |   35 +
 .../v2/c3p0/test/AlwaysFailConnectionTester.java   |   64 +
 .../com/mchange/v2/c3p0/test/C3P0BenchmarkApp.java |  732 ++++++
 .../v2/c3p0/test/ConnectionDispersionTest.java     |  233 ++
 .../test/FreezableDriverManagerDataSource.java     |  283 +++
 .../com/mchange/v2/c3p0/test/JavaBeanRefTest.java  |   51 +
 .../com/mchange/v2/c3p0/test/JndiBindTest.java     |  101 +
 .../com/mchange/v2/c3p0/test/JndiLookupTest.java   |   75 +
 .../com/mchange/v2/c3p0/test/ListTablesTest.java   |   59 +
 .../v2/c3p0/test/LoadPoolBackedDataSource.java     |  244 ++
 .../test/OneThreadRepeatedInsertOrQueryTest.java   |  163 ++
 .../v2/c3p0/test/PSLoadPoolBackedDataSource.java   |  226 ++
 .../mchange/v2/c3p0/test/ProxyWrappersTest.java    |   73 +
 .../mchange/v2/c3p0/test/RawConnectionOpTest.java  |  108 +
 .../com/mchange/v2/c3p0/test/StatsTest.java        |  112 +
 .../v2/c3p0/test/TestConnectionCustomizer.java     |   42 +
 .../com/mchange/v2/c3p0/test/TestRefSerStuff.java  |  185 ++
 .../v2/c3p0/test/junit/C3P0JUnitTestCaseBase.java  |   62 +
 .../ConnectionPropertiesResetJUnitTestCase.java    |  106 +
 ...MarshallUnmarshallDataSourcesJUnitTestCase.java |  111 +
 .../c3p0/util/CloseReportingConnectionWrapper.java |   40 +
 .../v2/c3p0/util/ConnectionEventSupport.java       |   76 +
 .../com/mchange/v2/c3p0/util/TestUtils.java        |  182 ++
 .../mchange/v2/cfg/BasicMultiPropertiesConfig.java |  286 +++
 .../v2/cfg/CombinedMultiPropertiesConfig.java      |  102 +
 .../com/mchange/v2/cfg/MultiPropertiesConfig.java  |  132 ++
 .../BasicMultiPropertiesConfigJUnitTestCase.java   |   56 +
 .../v2/coalesce/AbstractStrongCoalescer.java       |   54 +
 .../mchange/v2/coalesce/AbstractWeakCoalescer.java |   61 +
 .../com/mchange/v2/coalesce/CoalesceChecker.java   |   40 +
 .../mchange/v2/coalesce/CoalesceIdenticator.java   |   40 +
 src/classes/com/mchange/v2/coalesce/Coalescer.java |   33 +
 .../com/mchange/v2/coalesce/CoalescerFactory.java  |   98 +
 .../com/mchange/v2/coalesce/CoalescerIterator.java |   43 +
 .../com/mchange/v2/coalesce/StrongCcCoalescer.java |   37 +
 .../mchange/v2/coalesce/StrongEqualsCoalescer.java |   37 +
 .../com/mchange/v2/coalesce/SyncedCoalescer.java   |   43 +
 .../com/mchange/v2/coalesce/WeakCcCoalescer.java   |   37 +
 .../mchange/v2/coalesce/WeakEqualsCoalescer.java   |   36 +
 .../com/mchange/v2/codegen/CodegenUtils.java       |  175 ++
 .../com/mchange/v2/codegen/IndentedWriter.java     |   35 +
 .../bean/BeanExtractingGeneratorExtension.java     |  121 +
 .../com/mchange/v2/codegen/bean/BeangenUtils.java  |  247 ++
 .../com/mchange/v2/codegen/bean/ClassInfo.java     |   35 +
 .../v2/codegen/bean/CloneableExtension.java        |  122 +
 .../CompleteConstructorGeneratorExtension.java     |   67 +
 .../bean/CopyConstructorGeneratorExtension.java    |   73 +
 ...plicitDefaultConstructorGeneratorExtension.java |   48 +
 ...ExplicitPropsConstructorGeneratorExtension.java |  120 +
 .../v2/codegen/bean/GeneratorExtension.java        |   42 +
 .../bean/IndirectingSerializableExtension.java     |  153 ++
 .../bean/InnerBeanPropertyBeanGenerator.java       |  200 ++
 .../codegen/bean/ParsedPropertyBeanDocument.java   |  180 ++
 .../com/mchange/v2/codegen/bean/Property.java      |   40 +
 .../v2/codegen/bean/PropertyBeanGenerator.java     |   31 +
 .../v2/codegen/bean/PropertyComparator.java        |   35 +
 .../PropertyMapConstructorGeneratorExtension.java  |   95 +
 .../bean/PropertyReferenceableExtension.java       |  112 +
 .../bean/PropsToStringGeneratorExtension.java      |   89 +
 .../mchange/v2/codegen/bean/ResolvedClassInfo.java |   30 +
 .../mchange/v2/codegen/bean/ResolvedProperty.java  |   29 +
 .../v2/codegen/bean/SerializableExtension.java     |  201 ++
 .../mchange/v2/codegen/bean/SimpleClassInfo.java   |   62 +
 .../mchange/v2/codegen/bean/SimpleProperty.java    |   94 +
 .../codegen/bean/SimplePropertyBeanGenerator.java  |  610 +++++
 .../v2/codegen/bean/SimplePropertyMask.java        |   64 +
 ...pleStateBeanImportExportGeneratorExtension.java |  108 +
 .../mchange/v2/codegen/bean/WrapperClassInfo.java  |   40 +
 .../mchange/v2/codegen/bean/WrapperProperty.java   |   67 +
 .../v2/codegen/intfc/DelegatorGenerator.java       |  259 ++
 .../com/mchange/v2/debug/DebugConstants.java       |   31 +
 .../v2/debug/ThreadNameStackTraceRecorder.java     |  135 ++
 .../v2/encounter/AbstractEncounterCounter.java     |   57 +
 .../com/mchange/v2/encounter/EncounterCounter.java |   32 +
 .../v2/encounter/EqualityEncounterCounter.java     |   33 +
 .../ChangeNotifyingSynchronizedIntHolder.java      |   98 +
 .../mchange/v2/holders/SynchronizedIntHolder.java  |   73 +
 .../mchange/v2/holders/ThreadSafeIntHolder.java    |   30 +
 src/classes/com/mchange/v2/io/IndentedWriter.java  |  154 ++
 src/classes/com/mchange/v2/lang/Coerce.java        |  138 ++
 src/classes/com/mchange/v2/lang/ObjectUtils.java   |   48 +
 .../com/mchange/v2/lang/ThreadGroupUtils.java      |   42 +
 src/classes/com/mchange/v2/lang/ThreadUtils.java   |   71 +
 src/classes/com/mchange/v2/lang/VersionUtils.java  |  182 ++
 src/classes/com/mchange/v2/log/FallbackMLog.java   |  357 +++
 src/classes/com/mchange/v2/log/LogUtils.java       |   48 +
 src/classes/com/mchange/v2/log/MLevel.java         |  155 ++
 src/classes/com/mchange/v2/log/MLog.java           |  251 ++
 src/classes/com/mchange/v2/log/MLogClasses.java    |   36 +
 src/classes/com/mchange/v2/log/MLogger.java        |   78 +
 .../com/mchange/v2/log/NameTransformer.java        |   41 +
 src/classes/com/mchange/v2/log/PackageNames.java   |   43 +
 .../com/mchange/v2/log/jdk14logging/Jdk14MLog.java |  390 +++
 .../com/mchange/v2/log/log4j/Log4jMLog.java        |  313 +++
 .../com/mchange/v2/management/ManagementUtils.java |   96 +
 .../mchange/v2/naming/JavaBeanObjectFactory.java   |  159 ++
 .../mchange/v2/naming/JavaBeanReferenceMaker.java  |  176 ++
 .../com/mchange/v2/naming/ReferenceIndirector.java |  117 +
 .../com/mchange/v2/naming/ReferenceMaker.java      |   33 +
 .../com/mchange/v2/naming/ReferenceableUtils.java  |  177 ++
 .../mchange/v2/resourcepool/BasicResourcePool.java | 2085 +++++++++++++++++
 .../v2/resourcepool/BasicResourcePoolFactory.java  |  362 +++
 .../CannotAcquireResourceException.java            |   39 +
 .../com/mchange/v2/resourcepool/ResourcePool.java  |  141 ++
 .../mchange/v2/resourcepool/ResourcePoolEvent.java |   75 +
 .../v2/resourcepool/ResourcePoolEventSupport.java  |  129 +
 .../v2/resourcepool/ResourcePoolException.java     |   41 +
 .../v2/resourcepool/ResourcePoolFactory.java       |  189 ++
 .../v2/resourcepool/ResourcePoolListener.java      |   37 +
 .../mchange/v2/resourcepool/ResourcePoolUtils.java |   48 +
 .../mchange/v2/resourcepool/TimeoutException.java  |   39 +
 src/classes/com/mchange/v2/ser/IndirectPolicy.java |   39 +
 .../com/mchange/v2/ser/IndirectlySerialized.java   |   33 +
 src/classes/com/mchange/v2/ser/Indirector.java     |   29 +
 .../com/mchange/v2/ser/SerializableUtils.java      |  171 ++
 .../v2/ser/UnsupportedVersionException.java        |   35 +
 src/classes/com/mchange/v2/sql/SqlUtils.java       |  118 +
 .../v2/sql/filter/FilterCallableStatement.java     |  523 +++++
 .../mchange/v2/sql/filter/FilterConnection.java    |  160 ++
 .../v2/sql/filter/FilterDatabaseMetaData.java      |  542 +++++
 .../v2/sql/filter/FilterPreparedStatement.java     |  285 +++
 .../com/mchange/v2/sql/filter/FilterResultSet.java |  479 ++++
 .../com/mchange/v2/sql/filter/FilterStatement.java |  159 ++
 .../com/mchange/v2/sql/filter/RecreatePackage.java |   97 +
 .../SynchronizedFilterCallableStatement.java       |  523 +++++
 .../sql/filter/SynchronizedFilterConnection.java   |  160 ++
 .../filter/SynchronizedFilterDatabaseMetaData.java |  542 +++++
 .../SynchronizedFilterPreparedStatement.java       |  285 +++
 .../v2/sql/filter/SynchronizedFilterResultSet.java |  479 ++++
 .../v2/sql/filter/SynchronizedFilterStatement.java |  159 ++
 .../v2/sql/junit/SqlUtilsJUnitTestCase.java        |   48 +
 .../com/mchange/v2/util/DoubleWeakHashMap.java     |  577 +++++
 .../mchange/v2/util/ResourceClosedException.java   |   67 +
 .../util/junit/DoubleWeakHashMapJUnitTestCase.java |  108 +
 .../impl/DriverManagerDataSourceBase.beangen-xml   |   71 +
 .../v2/c3p0/impl/JndiRefDataSourceBase.beangen-xml |   53 +
 .../c3p0/impl/PoolBackedDataSourceBase.beangen-xml |   56 +
 ...WrapperConnectionPoolDataSourceBase.beangen-xml |  252 ++
 src/dist-static/CHANGELOG                          | 1179 ++++++++++
 src/dist-static/LICENSE                            |  504 ++++
 src/dist-static/README                             |   20 +
 src/dist-static/TODO                               |   28 +
 src/dist-static/examples/JndiBindDataSource.java   |   80 +
 src/dist-static/examples/UseJndiDataSource.java    |   86 +
 .../examples/UsePoolBackedDataSource.java          |   83 +
 .../examples/UseUnpooledDataSource.java            |   81 +
 src/dist-static/examples/c3p0-service.xml          |   48 +
 src/doc/arrow_sm.png                               |  Bin 0 -> 375 bytes
 src/doc/index.html                                 | 2474 ++++++++++++++++++++
 src/docweb/docwebapp/WEB-INF/jboss-web.xml         |    9 +
 src/docweb/docwebapp/WEB-INF/web.xml               |    7 +
 src/docweb/docwebear/META-INF/application.xml      |   15 +
 .../com/mchange/v2/cfg/junit/a.properties          |    1 +
 .../com/mchange/v2/cfg/junit/b.properties          |    1 +
 .../com/mchange/v2/cfg/vmConfigResourcePaths.txt   |   11 +
 .../mchange/v2/log/default-mchange-log.properties  |    3 +
 test-properties/c3p0-config.xml                    |   38 +
 test-properties/c3p0.properties                    |   54 +
 test-properties/log4j.properties                   |   17 +
 test-properties/logging.properties                 |   66 +
 version.properties                                 |    3 +
 316 files changed, 52028 insertions(+)

diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..a9fbbd5
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src/classes"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/.project b/.project
new file mode 100644
index 0000000..6bc67db
--- /dev/null
+++ b/.project
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>c3p0</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+	</buildSpec>
+	<natures>
+	</natures>
+</projectDescription>
diff --git a/README-SRC b/README-SRC
new file mode 100644
index 0000000..61828a8
--- /dev/null
+++ b/README-SRC
@@ -0,0 +1,16 @@
+Hi.
+
+Building c3p0 should be as easy as editing build.properties and typing ant.
+Please send any feedback / questions to Steve Waldman <swaldman at mchange.com>
+
+All of the test related properties are very optional. There are various
+test tasks in the build file, but they are ad-hoc and unsupported. Someday
+I plan to get with jUnit and do real testing, but that day has not yet come. 
+Until then, you, my dear user, are my primary testing engine. Sorry.
+
+      Steve Waldman <swaldman at mchange.com>
+      Machinery For Change, Inc.
+
+
+
+
diff --git a/build.properties b/build.properties
new file mode 100644
index 0000000..1caa5bc
--- /dev/null
+++ b/build.properties
@@ -0,0 +1,90 @@
+# >> BASICS <<
+
+#
+# You'll need to supply at least one of j2ee.classpath
+# or j2ee.jar.file.base.dir. All jar files under
+# ${j2ee.jar.file.base.dir} or its subdirectories
+# will be added to the effective classpath of the project.
+#
+
+#j2ee.classpath=
+#j2ee.jar.base.dir=
+
+# >> DEBUGGING AND TRACING <<
+
+# Set this to true if you want logging enabled for logging levels below INFO.
+# If debug is not set, logging code for these messages will be eliminated from
+# the compiled code by virtue of "if (false) { ... }" blocks.
+
+#c3p0-build.debug=
+
+# Set trace to an integer between 0 and 10 (inclusive) to control how the level
+# of detail of debug logging messages. Only makes a difference if c3p0.debug is
+# set to true above. Default to 5 if unset.
+
+#c3p0-build.trace=
+
+# NOTE: You must still configure your logging library to log or display these 
+# debug level messages if you actually want to see any change!
+
+#----------------------------------------------------------------------------
+
+# >> OPTIONAL LIBRARY SUPPRT <<
+
+#
+# You'll only need this property if you want to
+# build-in optional log4j support.
+#
+
+#log4j.jar.file=
+
+#
+# You'll only need this property if you want to
+# build the jar of utilities specific to the
+# oracle-thin jdbc driver / dbms
+#
+
+#oracle-thin.jdbc.jar.file=
+
+#----------------------------------------------------------------------------
+
+# >> OPTIONAL TEST SUPPORT
+
+#
+# this stuff is only required if you want to run
+# the various tests. very optional
+#
+
+#test.jdbc.driver.jar.file=
+#test.jdbc.drivers=
+#test.jdbc.url=
+#test.jdbc.user=
+#test.jdbc.password=
+
+#
+# required if you want to run junit tests
+#
+
+#junit.jar.file
+
+# >> VERY VERY OPTIONAL DOCS-TO-WEB SUPPORT
+
+#
+# this stuff is only required if you want to deploy
+# an ear file containing c3p0's docs to a J2EE appserver.
+# via scp. Requires an available executable "scp".
+# 
+# this is a convenience for c3p0's developer, not
+# really intended for other users. just leave blank
+#
+# note that virtual.host modifies a jboss-web.xml file,
+# will do nothing if you are deploying to some other
+# app server
+#
+
+#docwebapp.context.root=
+#docwebapp.virtual.host=
+#docwebear.deploy.user=
+#docwebear.deploy.host=
+#docwebear.deploy.path=
+
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..cee7c21
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,772 @@
+<project name="c3p0" default="dist">
+
+	<!-- ignore the CLASSPATH environment variable. force builds to specify classpaths -->
+	<property name="build.sysclasspath" value="ignore" />
+
+	<property file="private/build.properties" />
+	<property file="build.properties" />
+	<property file="version.properties" />
+
+	<property name="c3p0.name" value="c3p0-${c3p0.version}" />
+	<property name="bindist.name" value="${c3p0.name}.bin" />
+	<property name="srcdist.name" value="${c3p0.name}.src" />
+	<property name="src.dir" value="src" />
+	<property name="empty.src.dir" value="${src.dir}/empty" />
+	<property name="java.src.dir" value="${src.dir}/classes" />
+	<property name="rsrc.src.dir" value="${src.dir}/resources" />
+	<property name="codegen.src.dir" value="${src.dir}/codegen" />
+	<property name="doc.src.dir" value="${src.dir}/doc" />
+	<property name="docwebapp.src.dir" value="${src.dir}/docweb/docwebapp" />
+	<property name="docwebear.src.dir" value="${src.dir}/docweb/docwebear" />
+	<property name="static.dist.src" value="${src.dir}/dist-static" />
+	<property name="relproj.dir" value="relproj" />
+	<property name="relproj.dist.dir" value="${relproj.dir}/dist" />
+	<property name="test.props.dir" value="test-properties" />
+	<property name="test.logging.props.file" location="${test.props.dir}/logging.properties" />
+	<property name="build.dir" value="build" />
+	<property name="build.codegen.dir"     value="${build.dir}/codegen" />
+	<property name="build.classes.dir"     value="${build.dir}/classes" />
+	<property name="build.apidocs.dir"     value="${build.dir}/apidocs" />
+	<property name="build.docwebapp.dir"   value="${build.dir}/docweb/docwebapp" />
+	<property name="build.docwebear.dir"   value="${build.dir}/docweb/docwebear" />
+	<property name="build.testresults.dir" value="${build.dir}/testresults" />
+	<property name="build.jar.file"         value="${build.dir}/${c3p0.name}.jar" />
+	<property name="build.jdk13.java.src.dir"      value="${build.dir}/jdk13src" />
+	<property name="build.jdk13.build.dir"         value="${build.dir}/jdk13build" />
+	<property name="build.jdk13.build.classes.dir" value="${build.jdk13.build.dir}/classes" />
+	<property name="build.jar.file.jdk13"           value="${build.dir}/${c3p0.name}-jdk1.3.jar" />		
+	<property name="dbms.dir" value="dbms" />
+	<property name="dbms.oracle.thin.antproj.dir" value="${dbms.dir}/oracle-thin" />
+	<property name="dbms.oracle.thin.antproj.dist.dir" value="${dbms.oracle.thin.antproj.dir}/dist" />
+	<property name="test.classes.dir" value="${build.dir}/testclasses" />
+	<property name="dist.dir" value="dist" />
+	<property name="license.header.file" value="src/legal.prepend" />
+	<property name="open.dist" value="${dist.dir}/${bindist.name}" />
+	<property name="open.dist.doc.dir" value="${open.dist}/doc" />
+	<property name="open.dist.lib.dir" value="${open.dist}/lib" />
+	<property name="docwebapp.war.file.name" value="docweb.war" />
+	<property name="docwebapp.war.file" value="${build.docwebear.dir}/${docwebapp.war.file.name}" />
+	<property name="docwebear.file" value="${build.dir}/c3p0-docweb.ear" />
+
+	<!-- these properties should be set externally if desired  -->
+	<!-- we set them here only to keep classpaths valid -->
+	<!-- when users do not set the path                         -->
+	<property name="j2ee.jar.base.dir" value="${empty.src.dir}" />
+	<property name="j2ee.jar.dir"      value="${empty.src.dir}" />
+
+	<!--
+   <property name="log4j.jar.file" value="" />
+   <property name="junit.jar.file" value="" />
+  -->
+
+	<!-- these properties should often be preempted in build.properties -->
+	<property name="c3p0-build.debug" value="false" />
+	<property name="c3p0-build.trace" value="5" />
+
+	<property name="c3p0.target.version" value="1.4" />
+
+	<path id="codegen-classpath">
+		<pathelement location="${build.classes.dir}" />
+		<pathelement path="${j2ee.classpath}" />
+		<fileset dir="${j2ee.jar.base.dir}" includes="**/*.jar" />
+		<fileset dir="${j2ee.jar.dir}" includes="*.jar" />
+	</path>
+
+	<property name="codegen.classpath" refid="codegen-classpath" />
+
+	<path id="build-classpath">
+		<pathelement location="${build.classes.dir}" />
+		<pathelement path="${j2ee.classpath}" />
+		<fileset dir="${j2ee.jar.base.dir}" includes="**/*.jar" />
+		<fileset dir="${j2ee.jar.dir}" includes="*.jar" />
+	</path>
+
+	<patternset id="common-excludes">
+		<exclude name="**/old/**" />	
+		<exclude name="**/bad/**" />	
+		<exclude name="**/off/**" />	
+		<exclude name="**/private/**" />	
+	</patternset>
+
+	<patternset id="init-codegen-classes">
+		<include name="com/mchange/v2/c3p0/codegen/**/*.java" />
+		<include name="com/mchange/v2/codegen/**/*.java" />
+		<include name="com/mchange/v2/log/*.java" />
+		<include name="com/mchange/v2/cfg/*.java" />
+		<include name="com/mchange/v2/io/IndentedWriter.java" />
+		<include name="com/mchange/v1/util/StringTokenizerUtils.java" />
+		<patternset refid="common-excludes" />
+	</patternset>
+
+	<patternset id="dist-jar-classes">
+		<!-- excludes stuff only used by the code generator and by tests-->
+		<exclude name="com/mchange/v2/codegen/bean/*.java" />
+		<exclude name="com/mchange/v2/codegen/*.java" />
+		<exclude name="com/mchange/v2/debug/DebugGen.java" />
+		<exclude name="com/mchange/v1/lang/GentleThread.java" />
+		<exclude name="com/mchange/v1/lang/NullUtils.java" />
+		<exclude name="com/mchange/v1/lang/Synchronizer.java" />
+		<exclude name="com/mchange/v1/lang/TVLUtils.java" />
+		<exclude name="com/mchange/v1/jvm/**" />
+		<exclude name="com/mchange/v1/lang/holders/**" />
+		<exclude name="com/mchange/v2/c3p0/codegen/**" />
+		<exclude name="com/mchange/v2/c3p0/test/**" />
+		<exclude name="**/junit/**" />
+		<exclude name="**/*JUnitTestCase.*" />
+		<patternset refid="common-excludes" />
+	</patternset>
+
+	<patternset id="test-only-classes">
+		<include name="com/mchange/v2/c3p0/test/**" />
+		<include name="**/junit/**" />
+		<include name="**/*JUnitTestCase.class" />
+	</patternset>
+	
+	<path id="test-classpath">
+		<pathelement location="${test.props.dir}" />
+		<pathelement location="${build.jar.file}" />
+		<pathelement location="${test.classes.dir}" />
+		<pathelement path="${j2ee.classpath}" />
+		<fileset dir="${j2ee.jar.base.dir}" includes="**/*.jar" />
+		<fileset dir="${j2ee.jar.dir}" includes="*.jar" />
+		<pathelement location="${test.jdbc.driver.jar.file}" />
+		<pathelement location="${log4j.jar.file}" />
+	</path>
+	
+	<target name="init">
+		<tstamp>
+			<!-- <format property="c3p0.timestamp" pattern="dd-MMMM-yyyy HH:mm:ss Z"/> -->
+
+			<!-- jdk 1.3 compatible -->
+			<format property="c3p0.timestamp" pattern="dd-MMMM-yyyy HH:mm:ss"/>
+		</tstamp>
+
+		<mkdir dir="${build.dir}" />
+		<mkdir dir="${build.codegen.dir}" />
+		<mkdir dir="${build.classes.dir}" />
+		<mkdir dir="${build.apidocs.dir}" />
+		<mkdir dir="${dist.dir}" />
+	</target>
+
+	<target name="clean" depends="dbms-oracle-thin-clean">
+		<delete dir="${build.dir}" />
+		<delete dir="${dist.dir}" />
+	</target>
+
+	<target name="relproj" depends="init" unless="up-to-date-relproj">
+		<ant dir="${relproj.dir}" target="dist" inheritAll="false" />
+	</target>
+
+	<target name="init-debuggen" depends="relproj">
+		<uptodate property="up-to-date-debugs" 
+                srcfile="build.properties"
+                targetfile="${build.codegen.dir}/com/mchange/Debug.java" />
+	</target>
+
+	<target name="debuggen" depends="init-debuggen" unless="up-to-date-debugs">
+		<java classname="com.mchange.v2.debug.DebugGen" fork="true" dir=".">
+			<sysproperty key="com.mchange.v2.log.MLog" value="com.mchange.v2.log.FallbackMLog" />
+			<classpath>
+				<fileset dir="${relproj.dist.dir}">
+					<include name="*.jar" />
+				</fileset>
+			</classpath>
+			<arg value="--packages=com.mchange" />
+			<arg value="--codebase=src/classes" />
+			<arg value="--outputbase=${build.codegen.dir}" />
+			<arg value="--recursive" />
+			<arg value="--debug=${c3p0-build.debug}" />
+			<arg value="--trace=${c3p0-build.trace}" />
+		</java>
+	</target>
+
+	<target name="subst">
+		<copy todir="${build.codegen.dir}">
+			<fileset dir="${java.src.dir}">
+				<include name="**/subst/**" />
+			</fileset>
+			<filterchain>
+				<replacetokens>
+					<token key="c3p0.version"     value="${c3p0.version}"/>
+					<token key="c3p0.debug"     value="${c3p0-build.debug}"/>
+					<token key="c3p0.trace"     value="${c3p0-build.trace}"/>
+					<token key="c3p0.timestamp" value="${c3p0.timestamp}"/>
+
+					<!-- NO LONGER USED THIS WAY junit test stuff only 
+	     <token key="test.jdbc.drivers" value="${test.jdbc.drivers}" />
+	     <token key="test.jdbc.url" value="${test.jdbc.url}" />
+	     <token key="test.jdbc.user" value="${test.jdbc.user}" />
+	     <token key="test.jdbc.password" value="${test.jdbc.password}" />
+	     -->
+				</replacetokens>
+			</filterchain>
+		</copy>
+	</target>
+
+	<target name="init-codegen" depends="debuggen,subst">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             classpathref="codegen-classpath"
+             debug="true">
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</src>
+			<patternset refid="init-codegen-classes" />
+		</javac>
+
+		<uptodate property="up-to-date-proxies" 
+                srcfile="${java.src.dir}/com/mchange/v2/c3p0/codegen/JdbcProxyGenerator.java"
+                targetfile="${build.codegen.dir}/com/mchange/v2/c3p0/impl/NewProxyConnection.java" />
+	</target>
+
+
+	<target name="beangen" depends="init-codegen">
+		<echo message="Some warnings are expected here. Don't worry about them." />
+		<apply executable="java" dest="${build.codegen.dir}">
+			<arg value="-Dcom.mchange.v2.log.MLog=com.mchange.v2.log.FallbackMLog" />
+			<arg value="-classpath" />
+			<arg path="${codegen.classpath}" />
+			<arg value="com.mchange.v2.c3p0.codegen.BeangenDataSourceGenerator" />
+			<srcfile />
+			<targetfile />
+			<fileset dir="${codegen.src.dir}" includes="**/*.beangen-xml">
+				<patternset refid="common-excludes" />
+			</fileset>
+			<mapper type="glob" from="*.beangen-xml" to="*.java" />
+		</apply>
+	</target>
+
+	<target name="newproxygen" depends="init-codegen" unless="up-to-date-proxies">
+		<java classname="com.mchange.v2.c3p0.codegen.JdbcProxyGenerator" fork="true" dir=".">
+			<sysproperty key="com.mchange.v2.log.MLog" value="com.mchange.v2.log.FallbackMLog" />
+			<classpath refid="codegen-classpath" />
+			<arg value="${build.codegen.dir}" />
+		</java>
+	</target>
+
+	<target name="codegen" depends="beangen,newproxygen" />
+
+	<target name="compile-common" depends="codegen">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             classpathref="build-classpath" 
+             debug="on">
+			<sourcepath>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</sourcepath>
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</src>
+			<exclude name="**/junit/**" />
+			<exclude name="**/*JUnitTestCase.*" />
+			<exclude name="**/subst/**" />
+			<exclude name="com/mchange/v2/log/log4j/**" />
+			<patternset refid="common-excludes" />
+		</javac>
+	</target>
+
+	<target name="compile-subst" depends="codegen">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             classpathref="build-classpath" 
+             debug="on">
+			<sourcepath>
+				<pathelement location="${build.codegen.dir}" />
+			</sourcepath>
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+			</src>
+			<include name="**/subst/**" />
+		</javac>
+	</target>
+
+	<target name="compile-log4j" depends="init" if="log4j.jar.file">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             debug="on">
+			<classpath>
+				<path refid="build-classpath" />
+				<pathelement location="${log4j.jar.file}" />
+			</classpath>
+			<sourcepath>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</sourcepath>
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</src>
+			<include name="com/mchange/v2/log/log4j/**" />
+		</javac>
+	</target>
+
+	<target name="compile-junit" depends="init" if="junit.jar.file">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             debug="on">
+			<classpath>
+				<path refid="build-classpath" />
+				<pathelement path="${junit.jar.file}" />
+			</classpath>
+			<sourcepath>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</sourcepath>
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</src>
+			<include name="**/junit/**" />
+			<include name="**/*JUnitTestCase.*" />
+		</javac>
+	</target>
+
+	<target name="compile" depends="codegen,compile-common,compile-subst,compile-log4j,compile-junit" />
+
+	<target name="jar" depends="compile">
+		<jar destfile="${build.jar.file}">
+			<manifest>
+				<attribute name="Extension-Name" value="com.mchange.v2.c3p0" />
+				<attribute name="Specification-Vendor" value="Machinery For Change, Inc." />
+				<attribute name="Specification-Version" value="1.0" />
+				<attribute name="Implementation-Vendor-Id" value="com.mchange" />
+				<attribute name="Implementation-Vendor" value="Machinery For Change, Inc." />
+				<attribute name="Implementation-Version" value="${c3p0.version}" />
+			</manifest>
+			<fileset dir="${build.classes.dir}">
+				<patternset refid="dist-jar-classes"/>
+			</fileset>
+			<fileset dir="${rsrc.src.dir}" />
+		</jar>
+	</target>
+
+	<target name="dbms-oracle-thin-ant">
+		<ant dir="${dbms.oracle.thin.antproj.dir}" target="${subproject.target}" inheritAll="false">
+			<property name="c3p0.version" value="${c3p0.version}" />
+			<property name="c3p0.jar.file" location="${build.jar.file}" />
+			<property name="oracle-thin.jdbc.jar.file" value="${oracle-thin.jdbc.jar.file}" />
+		</ant>
+	</target>
+
+	<target name="dbms-oracle-thin-clean">
+		<antcall target="dbms-oracle-thin-ant">
+			<param name="subproject.target" value="clean" />
+		</antcall>
+	</target>
+
+	<target name="dbms-oracle-thin" depends="jar" if="oracle-thin.jdbc.jar.file">
+		<echo message="oracle-thin.jdbc.jar.file: ${oracle-thin.jdbc.jar.file}" />
+		<antcall target="dbms-oracle-thin-ant">
+			<param name="subproject.target" value="dist" />
+		</antcall>
+	</target>
+
+	<target name="test-init" depends="jar">
+		<mkdir dir="${test.classes.dir}" />
+		<copy toDir="${test.classes.dir}">
+			<fileset dir="${build.classes.dir}">
+				<patternset refid="test-only-classes"/>
+			</fileset>
+		</copy>
+		<property name="testcp" refid="test-classpath" />
+		<echo message="test-classpath: ${testcp}" />
+	</target>
+
+	<target name="stats-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.StatsTest" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+
+			<!--
+           <jvmarg value="-ea" />
+	   <sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+	   <sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+	   <sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+	   -->
+
+			<!-- <jvmarg value="-Xrunhprof:file=/tmp/java.hprof,doe=y,format=b" /> -->
+			<!-- <jvmarg value="-verbose:class" /> -->
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="proxywrapper-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.ProxyWrappersTest" 
+         classpathref="test-classpath" 
+	 	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+			<!-- <jvmarg value="-Xrunhprof:file=/tmp/java.hprof,doe=y,format=b" /> -->
+			<!-- <jvmarg value="-verbose:class" /> -->
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="benchmark-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.C3P0BenchmarkApp" 
+         classpathref="test-classpath" 
+	 	 fork="true">
+			<!-- <jvmarg value="-Xrunhprof:cpu=times,file=/tmp/java.hprof,doe=y,format=a" /> -->
+			<!-- <jvmarg value="-server" /> -->
+			<!-- <jvmarg value="-Xprof" /> -->
+			<!-- <jvmarg value="-verbose:class" /> -->
+
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+
+			<!--
+			<jvmarg value="-ea" />
+			<sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+			<sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+			<sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+			-->
+
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+				<!-- we don't use cmd line args or -Djdbc.drivers any more. we set up test datasources via config files -->
+<!--
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+-->
+		</java>
+	</target>
+
+	<target name="rco-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.RawConnectionOpTest" 
+         classpathref="test-classpath" 
+	 	 fork="true">
+			<!-- <jvmarg value="-Xrunhprof:cpu=times,file=/tmp/java.hprof,doe=y,format=a" /> -->
+			<!-- <jvmarg value="-Xprof" /> -->
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+
+			<!--
+           <jvmarg value="-ea" />
+	   <sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+	   <sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+	   <sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+	   -->
+
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="load-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.LoadPoolBackedDataSource" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+
+           <jvmarg value="-ea" />
+	   <sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+	   <sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+	   <sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+
+			<!-- we don't use cmd line args or -Djdbc.drivers any more. we set up test datasources via config files -->
+			<!--
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+			-->
+		</java>
+	</target>
+
+	<target name="psload-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.PSLoadPoolBackedDataSource" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+
+	           <jvmarg value="-ea" />
+	   <sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+	   <sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+	   <sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+			
+			
+			<!-- we don't use cmd line args or -Djdbc.drivers any more. we set up test datasources via config files -->
+			<!--
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+			-->
+		</java>
+	</target>
+
+	<target name="dispersion-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.ConnectionDispersionTest" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="onethreadrepeat-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.OneThreadRepeatedInsertOrQueryTest" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="refser-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.TestRefSerStuff" 
+         classpathref="test-classpath" 
+	     fork="true">
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+	         <jvmarg value="-ea" />
+			
+			<!-- we don't use cmd line args or -Djdbc.drivers any more. we set up test datasources via config files -->
+			<!--
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+			-->
+		</java>
+	</target>
+
+	<target name="junit-tests" depends="test-init" if="junit.jar.file">
+		<mkdir dir="${build.testresults.dir}" />
+		<junit printsummary="true" showoutput="true" haltonfailure="true"> 
+			<classpath refid="test-classpath" />
+			<formatter type="plain"/>
+
+			<!--
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="c3p0.test.jdbc.url" value="${test.jdbc.url}" />
+			<sysproperty key="c3p0.test.jdbc.user" value="${test.jdbc.user}" />
+			<sysproperty key="c3p0.test.jdbc.password" value="${test.jdbc.password}" />
+			-->
+
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+			<batchtest fork="yes" toDir="${build.testresults.dir}">
+				<fileset dir="${build.classes.dir}">
+					<include name="**/*JUnitTestCase.class"/>
+				</fileset>
+			</batchtest>
+		</junit>
+
+		<!--
+	 <junit printsummary="withOutAndErr" >
+	   <test name="com.mchange.v2.c3p0.test.junit.MiscellaneousTopLevelJUnitTestCase"/>
+	   <classpath refid="test-classpath" />
+	   <formatter type="plain"/>
+	 </junit>
+	-->
+	</target>
+
+	<target name="javadocs" depends="init">
+		<javadoc packagenames="com.mchange.v2.c3p0"
+               sourcepath="${java.src.dir}"
+               destdir="${build.apidocs.dir}" 
+               classpathref="build-classpath" 
+               windowtitle="${c3p0.name} API Documentation"
+      />
+	</target>
+
+	<target name="jar-1.3" depends="jar">
+		<mkdir dir="${build.jdk13.java.src.dir}" />
+		<mkdir dir="${build.jdk13.build.classes.dir}" />
+		
+		<!-- filter away assertion lines -->
+		<!-- source is jdk1.3 compatible except for assertions                         -->
+		<!-- which (by adopted convention) are always one line beginning with "assert" -->
+		<!-- these lines often use the 1.4 method Thread.holdsLock()                   -->
+		<!-- for now, this is the only code that requires 1.4 in c3p0                  -->
+		<copy toDir="${build.jdk13.java.src.dir}">
+			<fileset dir="${java.src.dir}" includes="**/*.java" />
+			<filterchain>
+				<linecontainsregexp>
+			  		<regexp pattern="(?m)^(?!\s*assert\b).*$"/>
+				</linecontainsregexp>
+			</filterchain>
+		</copy>
+		<antcall target="jar" inheritAll="false">
+			<param name="java.src.dir" value="${build.jdk13.java.src.dir}" />
+			<param name="build.classes.dir" value="${build.jdk13.build.classes.dir}" />
+			<param name="c3p0.target.version" value="1.3" />
+			<param name="build.jar.file" value="${build.jar.file.jdk13}" />			
+			<param name="up-to-date-relproj" value="true" />
+		</antcall>
+	</target>	
+
+	<target name="basic-open-dist" depends="jar, javadocs, jar-1.3">
+		<copy toDir="${open.dist}">
+			<fileset dir="${static.dist.src}">
+				<exclude name="*~" />
+				<exclude name="examples/*.class" />
+				<patternset refid="common-excludes" />
+			</fileset>
+		</copy>
+		<mkdir dir="${open.dist.doc.dir}/apidocs" />
+		<copy toDir="${open.dist.doc.dir}">
+			<fileset dir="${doc.src.dir}" excludes="*.png"/>
+			<filterchain>
+				<replacetokens>
+					<token key="c3p0.version" value="${c3p0.version}"/>
+				</replacetokens>
+			</filterchain>
+		</copy>
+		<copy toDir="${open.dist.doc.dir}">
+			<fileset dir="${doc.src.dir}" includes="*.png"/>
+		</copy>
+		<copy toDir="${open.dist.doc.dir}/apidocs">
+			<fileset dir="${build.apidocs.dir}" />
+		</copy>
+		<copy file="${build.jar.file}" toDir="${open.dist.lib.dir}" />
+		<copy file="${build.jar.file.jdk13}" toDir="${open.dist.lib.dir}" />
+	</target>
+
+	<target name="docwebapp" depends="basic-open-dist">
+		<mkdir dir="${build.docwebapp.dir}" />
+		<copy toDir="${build.docwebapp.dir}">
+			<fileset dir="${docwebapp.src.dir}"/>
+			<filterchain>
+				<replacetokens>
+					<token key="virtual.host" value="${docwebapp.virtual.host}"/>
+				</replacetokens>
+			</filterchain>
+		</copy>
+	</target>
+
+	<target name="docwebear" depends="docwebapp">
+		<mkdir dir="${build.docwebear.dir}" />
+		<copy toDir="${build.docwebear.dir}">
+			<fileset dir="${docwebear.src.dir}"/>
+			<filterchain>
+				<replacetokens>
+					<token key="web.uri" value="${docwebapp.war.file.name}"/>
+					<token key="context.root" value="${docwebapp.context.root}"/>
+				</replacetokens>
+			</filterchain>
+		</copy>
+		<jar destfile="${docwebapp.war.file}">
+			<zipfileset dir="${build.docwebapp.dir}" />
+			<zipfileset dir="${open.dist.doc.dir}" />
+		</jar>
+		<jar destfile="${docwebear.file}">
+			<zipfileset dir="${build.docwebear.dir}" />
+		</jar>
+	</target>
+
+	<target name="docwebear-deploy" depends="docwebear">
+		<exec executable="scp">
+			<arg line="${docwebear.file} ${docwebear.deploy.user}@${docwebear.deploy.host}:${docwebear.deploy.path}" />
+		</exec>
+	</target>
+
+	<target name="oracle-thin-open-dist" depends="basic-open-dist, dbms-oracle-thin"  if="oracle-thin.jdbc.jar.file">
+		<copy toDir="${open.dist}">
+			<fileset dir="${dbms.oracle.thin.antproj.dist.dir}" />
+		</copy>
+	</target>
+
+	<target name="open-dist" depends="basic-open-dist, oracle-thin-open-dist" />
+
+	<target name="zip-dist" depends="open-dist">
+		<zip destfile="${dist.dir}/${bindist.name}.zip">
+			<zipfileset dir="${open.dist}" prefix="${c3p0.name}"/>
+		</zip>
+	</target>
+
+	<target name="tar-dist" depends="open-dist">
+		<tar destfile="${dist.dir}/${bindist.name}.tar">
+			<tarfileset dir="${open.dist}" prefix="${c3p0.name}"/>
+		</tar>
+	</target>
+
+	<target name="tgz-dist" depends="tar-dist">
+		<gzip zipfile="${dist.dir}/${bindist.name}.tgz" src="${dist.dir}/${bindist.name}.tar" />
+	</target>
+
+	<target name="bindist" depends="tgz-dist, zip-dist" />
+
+	<target name="dist" depends="bindist" />
+
+	<target name="srcdist" depends="init">
+		<zip destfile="${dist.dir}/${srcdist.name}.zip">
+			<zipfileset dir="." prefix="${srcdist.name}">
+				<exclude name="${build.dir}/**"/>
+				<exclude name="${dist.dir}/**"/>
+				<exclude name="**/*.class"/>
+				<patternset refid="common-excludes" />
+			</zipfileset>
+		</zip>
+		<tar destfile="${dist.dir}/${srcdist.name}.tar">
+			<tarfileset dir="." prefix="${srcdist.name}">
+				<exclude name="${build.dir}/**"/>
+				<exclude name="${dist.dir}/**"/>
+				<exclude name="**/*.class"/>
+				<patternset refid="common-excludes" />
+			</tarfileset>
+		</tar>
+		<gzip zipfile="${dist.dir}/${srcdist.name}.tgz" src="${dist.dir}/${srcdist.name}.tar" />
+	</target>
+
+	<target name="all" depends="dist,srcdist" />
+
+</project>
+
diff --git a/build.xml.bkup b/build.xml.bkup
new file mode 100644
index 0000000..1cf902c
--- /dev/null
+++ b/build.xml.bkup
@@ -0,0 +1,728 @@
+<project name="c3p0" default="dist">
+
+	<!-- ignore the CLASSPATH environment variable. force builds to specify classpaths -->
+	<property name="build.sysclasspath" value="ignore" />
+
+	<property file="private/build.properties" />
+	<property file="build.properties" />
+	<property file="version.properties" />
+
+	<property name="c3p0.name" value="c3p0-${c3p0.version}" />
+	<property name="bindist.name" value="${c3p0.name}.bin" />
+	<property name="srcdist.name" value="${c3p0.name}.src" />
+	<property name="src.dir" value="src" />
+	<property name="empty.src.dir" value="${src.dir}/empty" />
+	<property name="java.src.dir" value="${src.dir}/classes" />
+	<property name="rsrc.src.dir" value="${src.dir}/resources" />
+	<property name="codegen.src.dir" value="${src.dir}/codegen" />
+	<property name="doc.src.dir" value="${src.dir}/doc" />
+	<property name="docwebapp.src.dir" value="${src.dir}/docweb/docwebapp" />
+	<property name="docwebear.src.dir" value="${src.dir}/docweb/docwebear" />
+	<property name="static.dist.src" value="${src.dir}/dist-static" />
+	<property name="relproj.dir" value="relproj" />
+	<property name="relproj.dist.dir" value="${relproj.dir}/dist" />
+	<property name="test.props.dir" value="test-properties" />
+	<property name="test.logging.props.file" location="${test.props.dir}/logging.properties" />
+	<property name="build.dir" value="build" />
+	<property name="build.codegen.dir"     value="${build.dir}/codegen" />
+	<property name="build.classes.dir"     value="${build.dir}/classes" />
+	<property name="build.apidocs.dir"     value="${build.dir}/apidocs" />
+	<property name="build.docwebapp.dir"   value="${build.dir}/docweb/docwebapp" />
+	<property name="build.docwebear.dir"   value="${build.dir}/docweb/docwebear" />
+	<property name="build.testresults.dir" value="${build.dir}/testresults" />
+	<property name="dbms.dir" value="dbms" />
+	<property name="dbms.oracle.thin.antproj.dir" value="${dbms.dir}/oracle-thin" />
+	<property name="dbms.oracle.thin.antproj.dist.dir" value="${dbms.oracle.thin.antproj.dir}/dist" />
+	<property name="test.classes.dir" value="${build.dir}/testclasses" />
+	<property name="dist.dir" value="dist" />
+	<property name="license.header.file" value="src/legal.prepend" />
+	<property name="build.jar.file" value="build/${c3p0.name}.jar" />
+	<property name="open.dist" value="${dist.dir}/${bindist.name}" />
+	<property name="open.dist.doc.dir" value="${open.dist}/doc" />
+	<property name="open.dist.lib.dir" value="${open.dist}/lib" />
+	<property name="docwebapp.war.file.name" value="docweb.war" />
+	<property name="docwebapp.war.file" value="${build.docwebear.dir}/${docwebapp.war.file.name}" />
+	<property name="docwebear.file" value="${build.dir}/c3p0-docweb.ear" />
+
+	<!-- these properties should be set externally if desired  -->
+	<!-- we set them here only to keep classpaths valid -->
+	<!-- when users do not set the path                         -->
+	<property name="j2ee.jar.base.dir" value="${empty.src.dir}" />
+	<property name="j2ee.jar.dir"      value="${empty.src.dir}" />
+
+	<!--
+   <property name="log4j.jar.file" value="" />
+   <property name="junit.jar.file" value="" />
+  -->
+
+	<!-- these properties should often be preempted in build.properties -->
+	<property name="c3p0-build.debug" value="false" />
+	<property name="c3p0-build.trace" value="5" />
+
+	<property name="c3p0.target.version" value="1.3" />
+
+	<path id="codegen-classpath">
+		<pathelement location="${build.classes.dir}" />
+		<pathelement path="${j2ee.classpath}" />
+		<fileset dir="${j2ee.jar.base.dir}" includes="**/*.jar" />
+		<fileset dir="${j2ee.jar.dir}" includes="*.jar" />
+	</path>
+
+	<property name="codegen.classpath" refid="codegen-classpath" />
+
+	<path id="build-classpath">
+		<pathelement location="${build.classes.dir}" />
+		<pathelement path="${j2ee.classpath}" />
+		<fileset dir="${j2ee.jar.base.dir}" includes="**/*.jar" />
+		<fileset dir="${j2ee.jar.dir}" includes="*.jar" />
+	</path>
+
+	<patternset id="init-codegen-classes">
+		<include name="com/mchange/v2/c3p0/codegen/**/*.java" />
+		<include name="com/mchange/v2/codegen/**/*.java" />
+		<include name="com/mchange/v2/log/*.java" />
+		<include name="com/mchange/v2/cfg/*.java" />
+		<include name="com/mchange/v2/io/IndentedWriter.java" />
+		<include name="com/mchange/v1/util/StringTokenizerUtils.java" />
+		<exclude name="**/bad/**" />
+		<exclude name="**/old/**" />
+	</patternset>
+
+	<patternset id="dist-jar-classes">
+		<!-- excludes stuff only used by the code generator and by tests-->
+		<exclude name="com/mchange/v2/codegen/bean/*.java" />
+		<exclude name="com/mchange/v2/codegen/*.java" />
+		<exclude name="com/mchange/v2/debug/DebugGen.java" />
+		<exclude name="com/mchange/v1/lang/ClassUtils.java" />
+		<exclude name="com/mchange/v1/lang/GentleThread.java" />
+		<exclude name="com/mchange/v1/lang/NullUtils.java" />
+		<exclude name="com/mchange/v1/lang/Synchronizer.java" />
+		<exclude name="com/mchange/v1/lang/TVLUtils.java" />
+		<exclude name="com/mchange/v1/jvm/**" />
+		<exclude name="com/mchange/v1/lang/holders/**" />
+		<exclude name="com/mchange/v2/c3p0/codegen/**" />
+		<exclude name="com/mchange/v2/c3p0/test/**" />
+		<exclude name="**/junit/**" />
+		<exclude name="**/*JUnitTestCase.*" />
+		<exclude name="**/bad/**" />
+		<exclude name="**/old/**" />
+	</patternset>
+
+	<patternset id="test-only-classes">
+		<include name="com/mchange/v2/c3p0/test/**" />
+		<include name="**/junit/**" />
+		<include name="**/*JUnitTestCase.class" />
+	</patternset>
+
+	<path id="test-classpath">
+		<pathelement location="${test.props.dir}" />
+		<pathelement location="${build.jar.file}" />
+		<pathelement location="${test.classes.dir}" />
+		<pathelement path="${j2ee.classpath}" />
+		<fileset dir="${j2ee.jar.base.dir}" includes="**/*.jar" />
+		<fileset dir="${j2ee.jar.dir}" includes="*.jar" />
+		<pathelement location="${test.jdbc.driver.jar.file}" />
+		<pathelement location="${log4j.jar.file}" />
+	</path>
+
+	<target name="init">
+		<tstamp>
+			<!-- <format property="c3p0.timestamp" pattern="dd-MMMM-yyyy HH:mm:ss Z"/> -->
+			<format property="c3p0.timestamp" pattern="dd-MMMM-yyyy HH:mm:ss"/>
+			<!-- jdk 1.3 compatible -->
+		</tstamp>
+
+		<mkdir dir="${build.dir}" />
+		<mkdir dir="${build.codegen.dir}" />
+		<mkdir dir="${build.classes.dir}" />
+		<mkdir dir="${build.apidocs.dir}" />
+		<mkdir dir="${dist.dir}" />
+	</target>
+
+	<target name="clean" depends="dbms-oracle-thin-clean">
+		<delete dir="${build.dir}" />
+		<delete dir="${dist.dir}" />
+	</target>
+
+	<target name="relproj" depends="init">
+		<ant dir="${relproj.dir}" target="dist" inheritAll="false" />
+	</target>
+
+	<target name="init-debuggen" depends="relproj">
+		<uptodate property="up-to-date-debugs" 
+                srcfile="build.properties"
+                targetfile="${build.codegen.dir}/com/mchange/Debug.java" />
+	</target>
+
+	<target name="debuggen" depends="init-debuggen" unless="up-to-date-debugs">
+		<java classname="com.mchange.v2.debug.DebugGen" fork="true" dir=".">
+			<sysproperty key="com.mchange.v2.log.MLog" value="com.mchange.v2.log.FallbackMLog" />
+			<classpath>
+				<fileset dir="${relproj.dist.dir}">
+					<include name="*.jar" />
+				</fileset>
+			</classpath>
+			<arg value="--packages=com.mchange" />
+			<arg value="--codebase=src/classes" />
+			<arg value="--outputbase=${build.codegen.dir}" />
+			<arg value="--recursive" />
+			<arg value="--debug=${c3p0-build.debug}" />
+			<arg value="--trace=${c3p0-build.trace}" />
+		</java>
+	</target>
+
+	<target name="subst">
+		<copy todir="${build.codegen.dir}">
+			<fileset dir="${java.src.dir}">
+				<include name="**/subst/**" />
+			</fileset>
+			<filterchain>
+				<replacetokens>
+					<token key="c3p0.version"     value="${c3p0.version}"/>
+					<token key="c3p0.debug"     value="${c3p0-build.debug}"/>
+					<token key="c3p0.trace"     value="${c3p0-build.trace}"/>
+					<token key="c3p0.timestamp" value="${c3p0.timestamp}"/>
+
+					<!-- NO LONGER USED THIS WAY junit test stuff only 
+	     <token key="test.jdbc.drivers" value="${test.jdbc.drivers}" />
+	     <token key="test.jdbc.url" value="${test.jdbc.url}" />
+	     <token key="test.jdbc.user" value="${test.jdbc.user}" />
+	     <token key="test.jdbc.password" value="${test.jdbc.password}" />
+	     -->
+				</replacetokens>
+			</filterchain>
+		</copy>
+	</target>
+
+	<target name="init-codegen" depends="debuggen,subst">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             classpathref="codegen-classpath"
+             debug="true">
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</src>
+			<patternset refid="init-codegen-classes" />
+		</javac>
+
+		<uptodate property="up-to-date-proxies" 
+                srcfile="${java.src.dir}/com/mchange/v2/c3p0/codegen/JdbcProxyGenerator.java"
+                targetfile="${build.codegen.dir}/com/mchange/v2/c3p0/impl/NewProxyConnection.java" />
+	</target>
+
+
+	<target name="beangen" depends="init-codegen">
+		<apply executable="java" dest="${build.codegen.dir}">
+			<arg value="-Dcom.mchange.v2.log.MLog=com.mchange.v2.log.FallbackMLog" />
+			<arg value="-classpath" />
+			<arg path="${codegen.classpath}" />
+			<arg value="com.mchange.v2.c3p0.codegen.BeangenDataSourceGenerator" />
+			<srcfile />
+			<targetfile />
+			<fileset dir="${codegen.src.dir}" includes="**/*.beangen-xml">
+				<exclude name="**/bad/**" />
+				<exclude name="**/old/**" />
+				<exclude name="private/**" />
+			</fileset>
+			<mapper type="glob" from="*.beangen-xml" to="*.java" />
+		</apply>
+	</target>
+
+	<target name="newproxygen" depends="init-codegen" unless="up-to-date-proxies">
+		<java classname="com.mchange.v2.c3p0.codegen.JdbcProxyGenerator" fork="true" dir=".">
+			<sysproperty key="com.mchange.v2.log.MLog" value="com.mchange.v2.log.FallbackMLog" />
+			<classpath refid="codegen-classpath" />
+			<arg value="${build.codegen.dir}" />
+		</java>
+	</target>
+
+	<target name="codegen" depends="beangen,newproxygen" />
+
+	<target name="compile-common" depends="codegen">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             classpathref="build-classpath" 
+             debug="on">
+			<sourcepath>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</sourcepath>
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</src>
+			<exclude name="**/junit/**" />
+			<exclude name="**/*JUnitTestCase.*" />
+			<exclude name="**/subst/**" />
+			<exclude name="com/mchange/v2/log/log4j/**" />
+		</javac>
+	</target>
+
+	<target name="compile-subst" depends="codegen">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             classpathref="build-classpath" 
+             debug="on">
+			<sourcepath>
+				<pathelement location="${build.codegen.dir}" />
+			</sourcepath>
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+			</src>
+			<include name="**/subst/**" />
+		</javac>
+	</target>
+
+	<target name="compile-log4j" depends="init" if="log4j.jar.file">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             debug="on">
+			<classpath>
+				<path refid="build-classpath" />
+				<pathelement location="${log4j.jar.file}" />
+			</classpath>
+			<sourcepath>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</sourcepath>
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</src>
+			<include name="com/mchange/v2/log/log4j/**" />
+		</javac>
+	</target>
+
+	<target name="compile-junit" depends="init" if="junit.jar.file">
+		<javac destdir="${build.classes.dir}" 
+	     source="${c3p0.target.version}"
+	     target="${c3p0.target.version}"
+             debug="on">
+			<classpath>
+				<path refid="build-classpath" />
+				<pathelement path="${junit.jar.file}" />
+			</classpath>
+			<sourcepath>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</sourcepath>
+			<src>
+				<pathelement location="${build.codegen.dir}" />
+				<pathelement location="${java.src.dir}" />
+			</src>
+			<include name="**/junit/**" />
+			<include name="**/*JUnitTestCase.*" />
+		</javac>
+	</target>
+
+	<target name="compile" depends="codegen,compile-common,compile-subst,compile-log4j,compile-junit">
+	</target>
+
+	<target name="jar" depends="compile">
+		<jar destfile="${build.jar.file}">
+			<manifest>
+				<attribute name="Extension-Name" value="com.mchange.v2.c3p0" />
+				<attribute name="Specification-Vendor" value="Machinery For Change, Inc." />
+				<attribute name="Specification-Version" value="1.0" />
+				<attribute name="Implementation-Vendor-Id" value="com.mchange" />
+				<attribute name="Implementation-Vendor" value="Machinery For Change, Inc." />
+				<attribute name="Implementation-Version" value="${c3p0.version}" />
+			</manifest>
+			<fileset dir="${build.classes.dir}">
+				<patternset refid="dist-jar-classes"/>
+			</fileset>
+			<fileset dir="${rsrc.src.dir}" />
+		</jar>
+	</target>
+
+	<target name="dbms-oracle-thin-ant">
+		<ant dir="${dbms.oracle.thin.antproj.dir}" target="${subproject.target}" inheritAll="false">
+			<property name="c3p0.version" value="${c3p0.version}" />
+			<property name="c3p0.jar.file" location="${build.jar.file}" />
+			<property name="oracle-thin.jdbc.jar.file" value="${oracle-thin.jdbc.jar.file}" />
+		</ant>
+	</target>
+
+	<target name="dbms-oracle-thin-clean">
+		<antcall target="dbms-oracle-thin-ant">
+			<param name="subproject.target" value="clean" />
+		</antcall>
+	</target>
+
+	<target name="dbms-oracle-thin" depends="jar" if="oracle-thin.jdbc.jar.file">
+		<echo message="oracle-thin.jdbc.jar.file: ${oracle-thin.jdbc.jar.file}" />
+		<antcall target="dbms-oracle-thin-ant">
+			<param name="subproject.target" value="dist" />
+		</antcall>
+	</target>
+
+	<target name="test-init" depends="jar">
+		<mkdir dir="${test.classes.dir}" />
+		<copy toDir="${test.classes.dir}">
+			<fileset dir="${build.classes.dir}">
+				<patternset refid="test-only-classes"/>
+			</fileset>
+		</copy>
+		<property name="testcp" refid="test-classpath" />
+		<echo message="test-classpath: ${testcp}" />
+	</target>
+
+	<target name="stats-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.StatsTest" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+
+			<!--
+           <jvmarg value="-ea" />
+	   <sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+	   <sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+	   <sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+	   -->
+
+			<!-- <jvmarg value="-Xrunhprof:file=/tmp/java.hprof,doe=y,format=b" /> -->
+			<!-- <jvmarg value="-verbose:class" /> -->
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="proxywrapper-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.ProxyWrappersTest" 
+         classpathref="test-classpath" 
+	 	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+			<!-- <jvmarg value="-Xrunhprof:file=/tmp/java.hprof,doe=y,format=b" /> -->
+			<!-- <jvmarg value="-verbose:class" /> -->
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="benchmark-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.C3P0BenchmarkApp" 
+         classpathref="test-classpath" 
+	 	 fork="true">
+			<!-- <jvmarg value="-Xrunhprof:cpu=times,file=/tmp/java.hprof,doe=y,format=a" /> -->
+			<!-- <jvmarg value="-server" /> -->
+			<!-- <jvmarg value="-Xprof" /> -->
+			<!-- <jvmarg value="-verbose:class" /> -->
+
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+
+			<jvmarg value="-ea" />
+			<sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+			<sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+			<sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+<!--
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+-->
+		</java>
+	</target>
+
+	<target name="rco-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.RawConnectionOpTest" 
+         classpathref="test-classpath" 
+	 	 fork="true">
+			<!-- <jvmarg value="-Xrunhprof:cpu=times,file=/tmp/java.hprof,doe=y,format=a" /> -->
+			<!-- <jvmarg value="-Xprof" /> -->
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+
+			<!--
+           <jvmarg value="-ea" />
+	   <sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+	   <sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+	   <sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+	   -->
+
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="load-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.LoadPoolBackedDataSource" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+
+			<!--
+           <jvmarg value="-ea" />
+	   <sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+	   <sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+	   <sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+	   -->
+
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="psload-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.PSLoadPoolBackedDataSource" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+
+			<!--
+           <jvmarg value="-ea" />
+	   <sysproperty key="com.sun.management.jmxremote.port" value="38383" />
+	   <sysproperty key="com.sun.management.jmxremote.authenticate" value="false" />
+	   <sysproperty key="com.sun.management.jmxremote.ssl" value="false" />
+	   -->
+
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="dispersion-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.ConnectionDispersionTest" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="onethreadrepeat-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.OneThreadRepeatedInsertOrQueryTest" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="refser-test" depends="test-init">
+		<java 
+         classname="com.mchange.v2.c3p0.test.TestRefSerStuff" 
+         classpathref="test-classpath" 
+	 fork="true">
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="java.util.logging.config.file" value="${test.logging.props.file}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+			<arg value="${test.jdbc.url}" />
+			<arg value="${test.jdbc.user}" />
+			<arg value="${test.jdbc.password}" />
+		</java>
+	</target>
+
+	<target name="junit-tests" depends="test-init" if="junit.jar.file">
+		<mkdir dir="${build.testresults.dir}" />
+		<junit printsummary="true" showoutput="true" haltonfailure="true">
+			<classpath refid="test-classpath" />
+			<formatter type="plain"/>
+			<sysproperty key="jdbc.drivers" value="${test.jdbc.drivers}" />
+			<sysproperty key="c3p0.test.jdbc.url" value="${test.jdbc.url}" />
+			<sysproperty key="c3p0.test.jdbc.user" value="${test.jdbc.user}" />
+			<sysproperty key="c3p0.test.jdbc.password" value="${test.jdbc.password}" />
+			<syspropertyset>
+				<propertyref builtin="commandline" />
+			</syspropertyset>
+			<batchtest fork="yes" toDir="${build.testresults.dir}">
+				<fileset dir="${build.classes.dir}">
+					<include name="**/*JUnitTestCase.class"/>
+				</fileset>
+			</batchtest>
+		</junit>
+
+		<!--
+	 <junit printsummary="withOutAndErr" >
+	   <test name="com.mchange.v2.c3p0.test.junit.MiscellaneousTopLevelJUnitTestCase"/>
+	   <classpath refid="test-classpath" />
+	   <formatter type="plain"/>
+	 </junit>
+	-->
+	</target>
+
+	<target name="javadocs" depends="init">
+		<javadoc packagenames="com.mchange.v2.c3p0"
+               sourcepath="${java.src.dir}"
+               destdir="${build.apidocs.dir}" 
+               classpathref="build-classpath" 
+               windowtitle="${c3p0.name} API Documentation"
+      />
+	</target>
+
+	<target name="basic-open-dist" depends="jar, javadocs">
+		<copy toDir="${open.dist}">
+			<fileset dir="${static.dist.src}">
+				<exclude name="*~" />
+				<exclude name="examples/*.class" />
+				<exclude name="**/old/**" />
+				<exclude name="**/bad/**" />
+				<exclude name="**/private/**" />
+			</fileset>
+		</copy>
+		<mkdir dir="${open.dist.doc.dir}/apidocs" />
+		<copy toDir="${open.dist.doc.dir}">
+			<fileset dir="${doc.src.dir}" excludes="*.png"/>
+			<filterchain>
+				<replacetokens>
+					<token key="c3p0.version" value="${c3p0.version}"/>
+				</replacetokens>
+			</filterchain>
+		</copy>
+		<copy toDir="${open.dist.doc.dir}">
+			<fileset dir="${doc.src.dir}" includes="*.png"/>
+		</copy>
+		<copy toDir="${open.dist.doc.dir}/apidocs">
+			<fileset dir="${build.apidocs.dir}" />
+		</copy>
+		<copy file="${build.jar.file}" toDir="${open.dist.lib.dir}" />
+	</target>
+
+	<target name="docwebapp" depends="basic-open-dist">
+		<mkdir dir="${build.docwebapp.dir}" />
+		<copy toDir="${build.docwebapp.dir}">
+			<fileset dir="${docwebapp.src.dir}"/>
+			<filterchain>
+				<replacetokens>
+					<token key="virtual.host" value="${docwebapp.virtual.host}"/>
+				</replacetokens>
+			</filterchain>
+		</copy>
+	</target>
+
+	<target name="docwebear" depends="docwebapp">
+		<mkdir dir="${build.docwebear.dir}" />
+		<copy toDir="${build.docwebear.dir}">
+			<fileset dir="${docwebear.src.dir}"/>
+			<filterchain>
+				<replacetokens>
+					<token key="web.uri" value="${docwebapp.war.file.name}"/>
+					<token key="context.root" value="${docwebapp.context.root}"/>
+				</replacetokens>
+			</filterchain>
+		</copy>
+		<jar destfile="${docwebapp.war.file}">
+			<zipfileset dir="${build.docwebapp.dir}" />
+			<zipfileset dir="${open.dist.doc.dir}" />
+		</jar>
+		<jar destfile="${docwebear.file}">
+			<zipfileset dir="${build.docwebear.dir}" />
+		</jar>
+	</target>
+
+	<target name="docwebear-deploy" depends="docwebear">
+		<exec executable="scp">
+			<arg line="${docwebear.file} ${docwebear.deploy.user}@${docwebear.deploy.host}:${docwebear.deploy.path}" />
+		</exec>
+	</target>
+
+	<target name="oracle-thin-open-dist" depends="basic-open-dist, dbms-oracle-thin"  if="oracle-thin.jdbc.jar.file">
+		<copy toDir="${open.dist}">
+			<fileset dir="${dbms.oracle.thin.antproj.dist.dir}" />
+		</copy>
+	</target>
+
+	<target name="open-dist" depends="basic-open-dist, oracle-thin-open-dist" />
+
+	<target name="zip-dist" depends="open-dist">
+		<zip destfile="${dist.dir}/${bindist.name}.zip">
+			<zipfileset dir="${open.dist}" prefix="${c3p0.name}"/>
+		</zip>
+	</target>
+
+	<target name="tar-dist" depends="open-dist">
+		<tar destfile="${dist.dir}/${bindist.name}.tar">
+			<tarfileset dir="${open.dist}" prefix="${c3p0.name}"/>
+		</tar>
+	</target>
+
+	<target name="tgz-dist" depends="tar-dist">
+		<gzip zipfile="${dist.dir}/${bindist.name}.tgz" src="${dist.dir}/${bindist.name}.tar" />
+	</target>
+
+	<target name="bindist" depends="tgz-dist, zip-dist" />
+
+	<target name="dist" depends="bindist" />
+
+	<target name="srcdist" depends="init">
+		<zip destfile="${dist.dir}/${srcdist.name}.zip">
+			<zipfileset dir="." prefix="${srcdist.name}">
+				<exclude name="${build.dir}/**"/>
+				<exclude name="${dist.dir}/**"/>
+				<exclude name="**/*.class"/>
+				<exclude name="**/old/**"/>
+				<exclude name="**/bad/**"/>
+				<exclude name="**/private/**"/>
+			</zipfileset>
+		</zip>
+		<tar destfile="${dist.dir}/${srcdist.name}.tar">
+			<tarfileset dir="." prefix="${srcdist.name}">
+				<exclude name="${build.dir}/**"/>
+				<exclude name="${dist.dir}/**"/>
+				<exclude name="**/*.class"/>
+				<exclude name="**/old/**"/>
+				<exclude name="**/bad/**"/>
+				<exclude name="**/private/**"/>
+			</tarfileset>
+		</tar>
+		<gzip zipfile="${dist.dir}/${srcdist.name}.tgz" src="${dist.dir}/${srcdist.name}.tar" />
+	</target>
+
+	<target name="all" depends="dist,srcdist" />
+
+</project>
+
diff --git a/dbms/oracle-thin/build.properties b/dbms/oracle-thin/build.properties
new file mode 100644
index 0000000..9410c87
--- /dev/null
+++ b/dbms/oracle-thin/build.properties
@@ -0,0 +1,10 @@
+#
+# You'll rarely (never) want to set these properties by hand... they
+# are set by the main c3p0 build script. They are listed here primarily
+# to document the dependencies of the dbms-specific build script.
+#
+
+c3p0-version=
+c3p0.jar.file=
+oracle-thin.jdbc.jar.file=
+
diff --git a/dbms/oracle-thin/build.xml b/dbms/oracle-thin/build.xml
new file mode 100644
index 0000000..0db4d81
--- /dev/null
+++ b/dbms/oracle-thin/build.xml
@@ -0,0 +1,68 @@
+<project name="c3p0-oracle-thin" default="dist">
+
+   <!-- ignore the CLASSPATH environment variable. force builds to specify classpaths -->
+   <property name="build.sysclasspath" value="ignore" />
+
+   <property file="build.properties" />
+
+   <property name="c3p0-oracle-thin.name" value="c3p0-oracle-thin-extras-${c3p0.version}" />
+   <property name="src.dir" value="src" />
+   <property name="java.src.dir" value="${src.dir}/classes" />
+   <property name="build.dir" value="build" />
+   <property name="build.classes.dir" value="${build.dir}/classes" />
+   <property name="build.apidocs.dir" value="${build.dir}/apidocs" />
+   <property name="dist.dir" value="dist" />
+   <property name="dist.lib.dir" value="${dist.dir}/lib" />
+   <property name="dist.apidocs.dir" value="${dist.dir}/doc/apidocs-oracle-thin" />
+   <property name="lib.jar.file" value="${dist.lib.dir}/${c3p0-oracle-thin.name}.jar" />
+
+   <path id="build-class-path">
+       <pathelement location="${c3p0.jar.file}" />
+       <pathelement location="${oracle-thin.jdbc.jar.file}" />
+   </path>
+
+   <target name="init">
+      <mkdir dir="${build.dir}" />
+      <mkdir dir="${dist.lib.dir}" />
+   </target>
+
+   <target name="clean">
+      <delete dir="${build.dir}" />
+      <delete dir="${dist.dir}" />
+   </target>
+
+   <target name="compile" depends="init">
+      <echo message="${c3p0.jar.file}" />
+      <echo message="${oracle-thin.jdbc.jar.file}" />
+      <mkdir dir="${build.classes.dir}" />
+      <javac srcdir="${java.src.dir}" 
+             destdir="${build.classes.dir}" 
+             classpathref="build-class-path" 
+             debug="on" />
+   </target>
+
+   <target name="javadocs" depends="init">
+      <mkdir dir="${build.apidocs.dir}" />
+      <javadoc packagenames="com.mchange.v2.c3p0.dbms"
+               sourcepath="${java.src.dir}"
+               destdir="${build.apidocs.dir}" 
+               classpathref="build-class-path" 
+               windowtitle="${c3p0-oracle-thin.name} API Documentation"
+      />
+   </target>
+
+   <target name="lib-jar" depends="compile">
+      <jar destfile="${lib.jar.file}">
+          <fileset dir="${build.classes.dir}" />
+      </jar>
+   </target>
+
+   <target name="dist" depends="lib-jar,javadocs">
+      <mkdir dir="${dist.apidocs.dir}" />
+      <copy toDir="${dist.apidocs.dir}">
+	<fileset dir="${build.apidocs.dir}" />
+      </copy>
+   </target>
+
+</project>
+
diff --git a/dbms/oracle-thin/src/classes/com/mchange/v2/c3p0/dbms/Debug.java b/dbms/oracle-thin/src/classes/com/mchange/v2/c3p0/dbms/Debug.java
new file mode 100644
index 0000000..f7d48b4
--- /dev/null
+++ b/dbms/oracle-thin/src/classes/com/mchange/v2/c3p0/dbms/Debug.java
@@ -0,0 +1,42 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+/********************************************************************
+ * This class generated by com.mchange.v2.debug.DebugGen
+ * and will probably be overwritten by the same! Edit at
+ * YOUR PERIL!!! Hahahahaha.
+ ********************************************************************/
+
+package com.mchange.v2.c3p0.dbms;
+
+import com.mchange.v2.debug.DebugConstants;
+
+final class Debug implements DebugConstants
+{
+	final static boolean DEBUG = true;
+	final static int     TRACE = TRACE_MED;
+
+	private Debug()
+	{}
+}
+
diff --git a/dbms/oracle-thin/src/classes/com/mchange/v2/c3p0/dbms/OracleUtils.java b/dbms/oracle-thin/src/classes/com/mchange/v2/c3p0/dbms/OracleUtils.java
new file mode 100644
index 0000000..397d044
--- /dev/null
+++ b/dbms/oracle-thin/src/classes/com/mchange/v2/c3p0/dbms/OracleUtils.java
@@ -0,0 +1,122 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.dbms;
+
+import java.lang.reflect.*;
+import java.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v2.sql.SqlUtils;
+import oracle.sql.BLOB;
+import oracle.sql.CLOB;
+import oracle.jdbc.driver.OracleConnection;
+
+/**
+ *  A convenience class for OracleUsers who wish to use Oracle-specific Connection API
+ *  without working directly with c3p0 raw connection operations.
+ */
+public final class OracleUtils
+{
+    final static Class[] CREATE_TEMP_ARGS = new Class[]{Connection.class, boolean.class, int.class};
+    
+    /**
+     *  Uses Oracle-specific API on the raw, underlying Connection to create a temporary BLOB.
+     *  <b>Users are responsible for calling freeTemporary on the returned BLOB prior to Connection close() / check-in!
+     *  c3p0 will <i>not</i> automatically clean up temporary BLOBs.</b>
+     *
+     * @param c3p0ProxyCon may be a c3p0 proxy for an <tt>oracle.jdbc.driver.OracleConnection</tt>, or an 
+     *        <tt>oracle.jdbc.driver.OracleConnection</tt> directly.
+     */
+    public static BLOB createTemporaryBLOB(Connection c3p0ProxyCon, boolean cache, int duration) throws SQLException
+    { 
+	if (c3p0ProxyCon instanceof C3P0ProxyConnection)
+	    {
+		try
+		    {
+			C3P0ProxyConnection castCon = (C3P0ProxyConnection) c3p0ProxyCon;
+			Method m = BLOB.class.getMethod("createTemporary", CREATE_TEMP_ARGS);
+			Object[] args = new Object[] {C3P0ProxyConnection.RAW_CONNECTION, Boolean.valueOf( cache ), new Integer( duration )};
+			return (BLOB) castCon.rawConnectionOperation(m, null, args);			
+		    }
+		catch (InvocationTargetException e)
+		    {
+			if (Debug.DEBUG)
+			    e.printStackTrace();
+			throw SqlUtils.toSQLException( e.getTargetException() );
+		    }
+		catch (Exception e)
+		    {
+			if (Debug.DEBUG)
+			    e.printStackTrace();
+			throw SqlUtils.toSQLException( e );
+		    }
+	    }
+	else if (c3p0ProxyCon instanceof OracleConnection)
+	    return BLOB.createTemporary( c3p0ProxyCon, cache, duration );
+	else
+	    throw new SQLException("Cannot create an oracle BLOB from a Connection that is neither an oracle.jdbc.driver.Connection, " +
+				   "nor a C3P0ProxyConnection wrapped around an oracle.jdbc.driver.Connection.");
+    }
+	
+    /**
+     *  Uses Oracle-specific API on the raw, underlying Connection to create a temporary CLOB.
+     *  <b>Users are responsible for calling freeTemporary on the returned BLOB prior to Connection close() / check-in!
+     *  c3p0 will <i>not</i> automatically clean up temporary CLOBs.</b>
+     *
+     * @param c3p0ProxyCon may be a c3p0 proxy for an <tt>oracle.jdbc.driver.OracleConnection</tt>, or an 
+     *        <tt>oracle.jdbc.driver.OracleConnection</tt> directly.
+     */
+    public static CLOB createTemporaryCLOB(Connection c3p0ProxyCon, boolean cache, int duration) throws SQLException
+    { 
+	if (c3p0ProxyCon instanceof C3P0ProxyConnection)
+	    {
+		try
+		    {
+			C3P0ProxyConnection castCon = (C3P0ProxyConnection) c3p0ProxyCon;
+			Method m = CLOB.class.getMethod("createTemporary", CREATE_TEMP_ARGS);
+			Object[] args = new Object[] {C3P0ProxyConnection.RAW_CONNECTION, Boolean.valueOf( cache ), new Integer( duration )};
+			return (CLOB) castCon.rawConnectionOperation(m, null, args);			
+		    }
+		catch (InvocationTargetException e)
+		    {
+			if (Debug.DEBUG)
+			    e.printStackTrace();
+			throw SqlUtils.toSQLException( e.getTargetException() );
+		    }
+		catch (Exception e)
+		    {
+			if (Debug.DEBUG)
+			    e.printStackTrace();
+			throw SqlUtils.toSQLException( e );
+		    }
+	    }
+	else if (c3p0ProxyCon instanceof OracleConnection)
+	    return CLOB.createTemporary( c3p0ProxyCon, cache, duration );
+	else
+	    throw new SQLException("Cannot create an oracle CLOB from a Connection that is neither an oracle.jdbc.driver.Connection, " +
+				   "nor a C3P0ProxyConnection wrapped around an oracle.jdbc.driver.Connection.");
+    }
+	
+    private OracleUtils()
+    {}
+}
diff --git a/relproj/build.xml b/relproj/build.xml
new file mode 100644
index 0000000..6d5ffa6
--- /dev/null
+++ b/relproj/build.xml
@@ -0,0 +1,26 @@
+<project name="c3p0-relproj" default="dist">
+
+   <!-- ignore the CLASSPATH environment variable. force builds to specify classpaths -->
+   <property name="build.sysclasspath" value="ignore" />
+
+   <property name="dist.dir" value="dist" />
+   <property name="debuggen.proj.dir" value="debuggen" />
+
+   <target name="init">
+      <mkdir dir="${dist.dir}" />
+   </target>
+
+   <target name="clean">
+     <delete dir="${dist.dir}" />
+     <ant dir="${debuggen.proj.dir}" target="clean" inheritAll="false" />
+   </target>
+
+   <target name="dist">
+     <ant dir="${debuggen.proj.dir}" target="jar" inheritAll="false" />
+     <copy todir="${dist.dir}">
+       <fileset dir="${debuggen.proj.dir}/dist" />
+     </copy>
+   </target>
+
+</project> 
+
diff --git a/relproj/debuggen/build.xml b/relproj/debuggen/build.xml
new file mode 100644
index 0000000..ede948c
--- /dev/null
+++ b/relproj/debuggen/build.xml
@@ -0,0 +1,71 @@
+<project name="debuggen" default="dist">
+
+   <!-- ignore the CLASSPATH environment variable. force builds to specify classpaths -->
+   <property name="build.sysclasspath" value="ignore" />
+
+   <property file="build.properties" />
+   <property file="version.properties" />
+
+   <property name="debuggen.name" value="debuggen-${debuggen.version}" />
+   <property name="src.dir" value="src" />
+   <property name="java.src.dir" value="${src.dir}/classes" />
+   <property name="app-rsrc.src.dir" value="${src.dir}/app-rsrc" />
+   <property name="build.dir" value="build" />
+   <property name="build.classes.dir" value="${build.dir}/classes" />
+   <property name="build.apidocs.dir" value="${build.dir}/apidocs" />
+   <property name="dist.dir" value="dist" />
+   <property name="app.jar.manifest" value="${app-rsrc.src.dir}/META-INF/manifest.src" />
+   <property name="jar.file" value="${dist.dir}/${debuggen.name}.jar" />
+   <property name="srcdist.name" value="${debuggen.name}-src" />
+
+   <target name="init">
+      <mkdir dir="${build.dir}" />
+      <mkdir dir="${dist.dir}" />
+   </target>
+
+   <target name="clean">
+      <delete dir="${build.dir}" />
+      <delete dir="${dist.dir}" />
+   </target>
+
+   <target name="compile" depends="init">
+      <mkdir dir="${build.classes.dir}" />
+      <javac srcdir="${java.src.dir}" 
+             destdir="${build.classes.dir}" 
+             debug="on" />
+   </target>
+
+   <target name="jar" depends="compile">
+      <jar destfile="${jar.file}" manifest="${app.jar.manifest}">
+          <fileset dir="${build.classes.dir}" />
+      </jar>
+   </target>
+
+   <target name="dist" depends="jar" />
+
+   <target name="srcdist">
+      <zip destfile="${dist.dir}/${srcdist.name}.zip">
+        <zipfileset dir="." prefix="${srcdist.name}">
+           <exclude name="${build.dir}/**"/>
+           <exclude name="${dist.dir}/**"/>
+           <exclude name="**/*.class"/>
+           <exclude name="**/old/**"/>
+           <exclude name="**/bad/**"/>
+        </zipfileset>
+      </zip>
+      <tar destfile="${dist.dir}/${srcdist.name}.tar">
+        <tarfileset dir="." prefix="${srcdist.name}">
+           <exclude name="${build.dir}/**"/>
+           <exclude name="${dist.dir}/**"/>
+           <exclude name="**/*.class"/>
+           <exclude name="**/old/**"/>
+           <exclude name="**/bad/**"/>
+        </tarfileset>
+      </tar>
+      <gzip zipfile="${dist.dir}/${srcdist.name}.tgz" src="${dist.dir}/${srcdist.name}.tar" />
+   </target>
+
+   <target name="all" depends="dist,srcdist" />
+
+</project>
+
diff --git a/relproj/debuggen/src/app-rsrc/META-INF/manifest.src b/relproj/debuggen/src/app-rsrc/META-INF/manifest.src
new file mode 100644
index 0000000..bbb1b71
--- /dev/null
+++ b/relproj/debuggen/src/app-rsrc/META-INF/manifest.src
@@ -0,0 +1,2 @@
+Main-Class: com.mchange.v2.debug.DebugGen
+
diff --git a/relproj/debuggen/src/classes/com/mchange/v1/io/WriterUtils.java b/relproj/debuggen/src/classes/com/mchange/v1/io/WriterUtils.java
new file mode 100644
index 0000000..b92676e
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v1/io/WriterUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.io;
+
+import java.io.Writer;
+import java.io.IOException;
+
+public final class WriterUtils
+{
+    public static void attemptClose(Writer os)
+    {
+      try
+	{if (os != null) os.close();}
+      catch (IOException e)
+	  {e.printStackTrace();}
+    }
+
+  private WriterUtils()
+    {}
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v1/lang/BooleanUtils.java b/relproj/debuggen/src/classes/com/mchange/v1/lang/BooleanUtils.java
new file mode 100644
index 0000000..105d930
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v1/lang/BooleanUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.lang;
+
+public final class BooleanUtils
+{
+    public static boolean parseBoolean(String str) throws IllegalArgumentException
+    {
+	if (str.equals("true"))
+	    return true;
+	else if (str.equals("false"))
+	    return false;
+	else
+	    throw new IllegalArgumentException("\"str\" is neither \"true\" nor \"false\".");
+    }
+
+    private BooleanUtils()
+    {}
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v1/util/ClosableResource.java b/relproj/debuggen/src/classes/com/mchange/v1/util/ClosableResource.java
new file mode 100644
index 0000000..a50aa57
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v1/util/ClosableResource.java
@@ -0,0 +1,40 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+/**
+ * An interface intended to be shared by the many sorts
+ * of objects that offer some kind of close method to
+ * cleanup resources they might be using. (I wish Sun
+ * had defined, and used, such an interface in the standard
+ * APIs.
+ */
+public interface ClosableResource
+{
+    /**
+     * forces the release of any resources that might be
+     * associated with this object.
+     */
+    public void close() throws Exception;
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v1/util/IteratorUtils.java b/relproj/debuggen/src/classes/com/mchange/v1/util/IteratorUtils.java
new file mode 100644
index 0000000..d78137a
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v1/util/IteratorUtils.java
@@ -0,0 +1,159 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import java.util.*;
+import java.lang.reflect.Array;
+
+public final class IteratorUtils
+{
+    public final static Iterator EMPTY_ITERATOR = new Iterator()
+    {
+	public boolean hasNext()
+	{ return false; }
+
+	public Object next()
+	{ throw new NoSuchElementException(); }
+
+	public void remove()
+	{ throw new IllegalStateException(); }
+    };
+
+    public static Iterator oneElementUnmodifiableIterator(final Object elem)
+    {
+	return new Iterator()
+	    {
+		boolean shot = false;
+
+		public boolean hasNext() { return (!shot); }
+
+		public Object next()
+		{
+		    if (shot)
+			throw new NoSuchElementException();
+		    else
+			{
+			    shot = true;
+			    return elem;
+			}
+		}
+
+		public void remove()
+		{ throw new UnsupportedOperationException("remove() not supported."); }
+	    };
+    }
+
+    public static boolean equivalent(Iterator ii, Iterator jj)
+    {
+	while (true)
+	    {
+		boolean ii_hasnext = ii.hasNext();
+		boolean jj_hasnext = jj.hasNext();
+		if (ii_hasnext ^ jj_hasnext)
+		    return false;
+		else if (ii_hasnext)
+		    {
+			Object iiNext = ii.next();
+			Object jjNext = jj.next();
+			if (iiNext == jjNext)
+			    continue;
+			else if (iiNext == null)
+			    return false;
+			else if (!iiNext.equals(jjNext))
+			    return false;
+		    }
+		else return true;
+	    }
+    }
+
+    public static ArrayList toArrayList(Iterator ii, int initial_capacity)
+    {
+	ArrayList out = new ArrayList(initial_capacity);
+	while (ii.hasNext())
+	    out.add(ii.next());
+	return out;
+    }
+
+    /**
+     * Fills an array with the contents of an iterator. If the array is too small,
+     * it will contain the first portion of the iterator. If the array can contain
+     * more elements than the iterator, extra elements are left untouched, unless
+     * null_terminate is set to true, in which case the element immediately following
+     * the last from the iterator is set to null. (This method is intended to make
+     * it easy to implement Collection.toArray(Object[] oo) methods...
+     *
+     * @param null_terminate iff there is extra space in the array, set the element
+     *        immediately after the last from the iterator to null.
+     */
+    public static void fillArray(Iterator ii, Object[] fillMe, boolean null_terminate)
+    {
+	int i = 0;
+	int len = fillMe.length;
+	while ( i < len && ii.hasNext() )
+	    fillMe[ i++ ] = ii.next();
+	if (null_terminate && i < len)
+	    fillMe[i] = null;
+    }
+
+    public static void fillArray(Iterator ii, Object[] fillMe)
+    { fillArray( ii, fillMe, false); }
+
+    /**
+     * @param null_terminate iff there is extra space in the array, set the element
+     *        immediately after the last from the iterator to null.
+     */
+    public static Object[] toArray(Iterator ii, int array_size, Class componentClass, boolean null_terminate)
+    {
+	Object[] out = (Object[]) Array.newInstance( componentClass, array_size );
+	fillArray(ii, out, null_terminate);
+	return out;
+    }
+
+    public static Object[] toArray(Iterator ii, int array_size, Class componentClass)
+    { return toArray( ii, array_size, componentClass, false ); }
+
+    /**
+     * Designed to help implement Collection.toArray(Object[] )methods... does
+     * the right thing if you can express an iterator and know the size of your
+     * Collection.
+     */
+    public static Object[] toArray(Iterator ii, int ii_size, Object[] maybeFillMe)
+    {
+	if (maybeFillMe.length >= ii_size)
+	    {
+		fillArray( ii, maybeFillMe, true );
+		return maybeFillMe;
+	    }
+	else
+	    {
+		Class componentType = maybeFillMe.getClass().getComponentType(); 
+		return toArray( ii, ii_size, componentType );
+	    }
+    }
+
+    private IteratorUtils()
+    {}
+}
+
+
diff --git a/relproj/debuggen/src/classes/com/mchange/v1/util/SetUtils.java b/relproj/debuggen/src/classes/com/mchange/v1/util/SetUtils.java
new file mode 100644
index 0000000..7d7e259
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v1/util/SetUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.util.AbstractSet;
+import java.util.HashSet;
+
+public final class SetUtils
+{
+    public static Set oneElementUnmodifiableSet(final Object elem)
+    {
+	return new AbstractSet()
+	    {
+		public Iterator iterator()
+		{ return IteratorUtils.oneElementUnmodifiableIterator( elem ); }
+
+		public int size() { return 1; }
+
+		public boolean isEmpty()
+		{ return false; }
+
+		public boolean contains(Object o) 
+		{ return o == elem; }
+
+	    };
+    }
+
+    public static Set setFromArray(Object[] array)
+    {
+	HashSet out = new HashSet();
+	for (int i = 0, len = array.length; i < len; ++i)
+	    out.add( array[i] );
+	return out;
+    }
+
+    public static boolean equivalentDisregardingSort(Set a, Set b)
+    {
+	return 
+	    a.containsAll( b ) &&
+	    b.containsAll( a );
+    }
+
+    /**
+     * finds a hash value which takes into account
+     * the value of all elements, such that two sets
+     * for which equivalentDisregardingSort(a, b) returns
+     * true will hashContentsDisregardingSort() to the same value
+     */
+    public static int hashContentsDisregardingSort(Set s)
+    {
+	int out = 0;
+	for (Iterator ii = s.iterator(); ii.hasNext(); )
+	    {
+		Object o = ii.next();
+		if (o != null) out ^= o.hashCode();
+	    }
+	return out;
+    }
+}
+
diff --git a/relproj/debuggen/src/classes/com/mchange/v1/util/StringTokenizerUtils.java b/relproj/debuggen/src/classes/com/mchange/v1/util/StringTokenizerUtils.java
new file mode 100644
index 0000000..5db6b5d
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v1/util/StringTokenizerUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import java.util.StringTokenizer;
+
+public final class StringTokenizerUtils
+{
+    public static String[] tokenizeToArray(String str, String delim, boolean returntokens)
+    {
+	StringTokenizer st = new StringTokenizer(str, delim, returntokens);
+	String[] strings = new String[st.countTokens()];
+	for (int i = 0; st.hasMoreTokens(); ++i)
+	   strings[i] = st.nextToken();
+	return strings;
+    }
+
+    public static String[] tokenizeToArray(String str, String delim)
+    {return tokenizeToArray(str, delim, false);}
+
+    public static String[] tokenizeToArray(String str)
+    {return tokenizeToArray(str, " \t\r\n");}
+}
+
+
+
+
diff --git a/relproj/debuggen/src/classes/com/mchange/v1/util/UIterator.java b/relproj/debuggen/src/classes/com/mchange/v1/util/UIterator.java
new file mode 100644
index 0000000..48371b7
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v1/util/UIterator.java
@@ -0,0 +1,42 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+/**
+ * Incomplete parent of "Unreliable Iterator"
+ * This is often bound to a scarce resource! Don't
+ * forget to close it when you are done!!!
+ *
+ * This interface is not intended to be implemented
+ * directly, but to be extended by subinterfaces
+ * that narrow the exceptions reasonably.
+ */
+public interface UIterator extends ClosableResource
+{
+    public boolean hasNext() throws Exception;
+    public Object  next()    throws Exception;
+    public void    remove()  throws Exception;
+    public void    close() throws Exception;
+}
+
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/cmdline/BadCommandLineException.java b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/BadCommandLineException.java
new file mode 100644
index 0000000..055852e
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/BadCommandLineException.java
@@ -0,0 +1,33 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cmdline;
+
+public class BadCommandLineException extends Exception
+{
+    public BadCommandLineException(String msg)
+    { super(msg); }
+
+    public BadCommandLineException()
+    { super(); }
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/cmdline/CommandLineUtils.java b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/CommandLineUtils.java
new file mode 100644
index 0000000..6d74474
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/CommandLineUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cmdline;
+
+public final class CommandLineUtils
+{
+    /**
+     * "Parses" a command line by making use several conventions:
+     * <UL>
+     * <LI> Certain arguments are considered "switches", by virtue
+     *      of being prefixed with some string, usually "-", "/", or "--"
+     * <LI> Switches may have arguments associated with them. This implementation
+     *      permits only a single argument per switch
+     * <LI> Switch arguments are determined via two conventions:
+     *      <OL>
+     *      <LI> If a switch is of the form "--switch=value" (where "--" is
+     *           set as the switch prefix), value is the switches argument.
+     *      <LI> If a switch is not of this form (simply "--switch"), then the
+     *           following item on the command line is considered the switch's
+     *           argument if and only if
+     *           <OL>
+     *           <LI> the argSwitches array contains the switch, and
+     *           <LI> the next item on the command line is not itself a switch
+     *           </OL>
+     *      </OL>
+     * </UL>
+     *
+     * @param argv the entire list of arguments, usually the argument to a main function
+     * @param switchPrefix the string which separates "switches" from regular command line args.
+     *        Must be non-null
+     * @param validSwitches a list of all the switches permissible for this command line.
+     *        If non-null, an UnexpectedSwitchException will be thrown if a switch not
+     *        in this list is encountered. Use null to accept any switches.
+     * @param requiredSwitches a list of all the switches required by this command line.
+     *        If non-null, an MissingSwitchException will be thrown if a switch
+     *        in this list is not present. Use null if no switches should be considered required.
+     * @param argSwitches a list of switches that should have an argument associated with them
+     *        If non-null, an MissingSwitchArgumentException will be thrown if a switch
+     *        in this list has no argument is not present. Use null if no switches should 
+     *        be considered to require arguments. However, this parameter is required if 
+     *        distinct items on a command line should be considered arguments to preceding
+     *        items. (For example, "f" must be an argSwitch for "-f myfile.txt" to be parsed
+     *        as switch and argument, but argSwitches is not required to parse "--file=myfile.txt"
+     */
+    public static ParsedCommandLine parse(String[] argv, 
+				   String switchPrefix, 
+				   String[] validSwitches,
+				   String[] requiredSwitches,
+				   String[] argSwitches)
+	throws BadCommandLineException
+    {
+	return new ParsedCommandLineImpl( argv,
+					  switchPrefix,
+					  validSwitches,
+					  requiredSwitches,
+					  argSwitches );
+    }
+
+    private CommandLineUtils()
+    {}
+}
+
+
+
+
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/cmdline/MissingSwitchException.java b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/MissingSwitchException.java
new file mode 100644
index 0000000..3c591dc
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/MissingSwitchException.java
@@ -0,0 +1,38 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cmdline;
+
+public class MissingSwitchException extends BadCommandLineException
+{
+    String sw;
+
+    MissingSwitchException(String msg, String sw)
+    {
+	super(msg);
+	this.sw = sw;
+    }
+
+    public String getMissingSwitch()
+    { return sw; }
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/cmdline/ParsedCommandLine.java b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/ParsedCommandLine.java
new file mode 100644
index 0000000..7b9e2b2
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/ParsedCommandLine.java
@@ -0,0 +1,34 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cmdline;
+
+public interface ParsedCommandLine
+{
+    public String[] getRawArgs();
+
+    public String   getSwitchPrefix();
+    public boolean  includesSwitch(String sw);
+    public String   getSwitchArg(String sw);
+    public String[] getUnswitchedArgs();
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/cmdline/ParsedCommandLineImpl.java b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/ParsedCommandLineImpl.java
new file mode 100644
index 0000000..9d2637e
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/ParsedCommandLineImpl.java
@@ -0,0 +1,124 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cmdline;
+
+import java.util.*;
+
+class ParsedCommandLineImpl implements ParsedCommandLine
+{
+    String[] argv; 
+    String   switchPrefix;
+
+    String[] unswitchedArgs;
+
+    //we are relying upon the fact that
+    //HashMaps are null-accepting collections
+    HashMap  foundSwitches = new HashMap(); 
+    
+    ParsedCommandLineImpl(String[] argv, 
+			  String switchPrefix, 
+			  String[] validSwitches,
+			  String[] requiredSwitches,
+			  String[] argSwitches)
+	throws BadCommandLineException
+    {
+	this.argv = argv;
+	this.switchPrefix = switchPrefix;
+
+	List unswitchedArgsList = new LinkedList();
+	int sp_len = switchPrefix.length();
+
+	for (int i = 0; i < argv.length; ++i)
+	    {
+		if (argv[i].startsWith(switchPrefix)) //okay, this is a switch
+		{
+		    String sw = argv[i].substring( sp_len );
+		    String arg = null;
+
+		    int eq = sw.indexOf('=');
+		    if ( eq >= 0 ) //equals convention
+			{
+			    arg = sw.substring( eq + 1 );
+			    sw = sw.substring( 0, eq );
+			}
+		    else if ( contains( sw, argSwitches ) ) //we expect an argument, next arg convention
+			{
+			    if (i < argv.length - 1 && !argv[i + 1].startsWith( switchPrefix) )
+				arg = argv[++i];
+			}
+
+		    if (validSwitches != null && ! contains( sw, validSwitches ) )
+			throw new UnexpectedSwitchException("Unexpected Switch: " + sw, sw);
+		    if (argSwitches != null && arg != null && ! contains( sw, argSwitches ))
+			throw new UnexpectedSwitchArgumentException("Switch \"" + sw +
+								    "\" should not have an " +
+								    "argument. Argument \"" +
+								    arg + "\" found.", sw, arg);
+		    foundSwitches.put( sw, arg );
+		}
+		else
+		    unswitchedArgsList.add( argv[i] );
+	    }
+
+	if (requiredSwitches != null)
+	    {
+		for (int i = 0; i < requiredSwitches.length; ++i)
+		    if (! foundSwitches.containsKey( requiredSwitches[i] ))
+			throw new MissingSwitchException("Required switch \"" + requiredSwitches[i] +
+							 "\" not found.", requiredSwitches[i]);
+	    }
+
+	unswitchedArgs = new String[ unswitchedArgsList.size() ];
+	unswitchedArgsList.toArray( unswitchedArgs );
+    }
+
+    public String getSwitchPrefix()
+    { return switchPrefix; }
+
+    public String[] getRawArgs()
+    { return (String[]) argv.clone(); }
+    
+    public boolean includesSwitch(String sw)
+    { return foundSwitches.containsKey( sw ); }
+
+    public String getSwitchArg(String sw)
+    { return (String) foundSwitches.get(sw); }
+
+    public String[] getUnswitchedArgs()
+    { return (String[]) unswitchedArgs.clone(); }
+
+    private static boolean contains(String string, String[] list)
+    {
+	for (int i = list.length; --i >= 0;)
+	    if (list[i].equals(string)) return true;
+	return false;
+    }
+    
+}
+
+
+
+
+
+
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/cmdline/UnexpectedSwitchArgumentException.java b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/UnexpectedSwitchArgumentException.java
new file mode 100644
index 0000000..28bf1da
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/UnexpectedSwitchArgumentException.java
@@ -0,0 +1,43 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cmdline;
+
+public class UnexpectedSwitchArgumentException extends BadCommandLineException
+{
+    String sw;
+    String arg;
+
+    UnexpectedSwitchArgumentException(String msg, String sw, String arg)
+    {
+	super(msg);
+	this.sw = sw;
+	this.arg = arg;
+    }
+
+    public String getSwitch()
+    { return sw; }
+
+    public String getUnexpectedArgument()
+    { return arg; }
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/cmdline/UnexpectedSwitchException.java b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/UnexpectedSwitchException.java
new file mode 100644
index 0000000..0f86bd2
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/cmdline/UnexpectedSwitchException.java
@@ -0,0 +1,38 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cmdline;
+
+public class UnexpectedSwitchException extends BadCommandLineException
+{
+    String sw;
+
+    UnexpectedSwitchException(String msg, String sw)
+    {
+	super(msg);
+	this.sw = sw;
+    }
+
+    public String getUnexpectedSwitch()
+    { return sw; }
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/debug/DebugConstants.java b/relproj/debuggen/src/classes/com/mchange/v2/debug/DebugConstants.java
new file mode 100644
index 0000000..1c3be1b
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/debug/DebugConstants.java
@@ -0,0 +1,31 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.debug;
+
+public interface DebugConstants
+{
+    public final static int TRACE_NONE = 0;
+    public final static int TRACE_MED  = 5;
+    public final static int TRACE_MAX  = 10;
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/debug/DebugGen.java b/relproj/debuggen/src/classes/com/mchange/v2/debug/DebugGen.java
new file mode 100644
index 0000000..e6159d6
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/debug/DebugGen.java
@@ -0,0 +1,371 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.debug;
+
+import java.io.*;
+import java.util.*;
+import com.mchange.v2.cmdline.*;
+import com.mchange.v2.io.FileIterator;
+import com.mchange.v2.io.DirectoryDescentUtils;
+import com.mchange.v1.io.WriterUtils;
+import com.mchange.v1.lang.BooleanUtils;
+import com.mchange.v1.util.SetUtils;
+import com.mchange.v1.util.StringTokenizerUtils;
+
+public final class DebugGen implements DebugConstants
+{
+    final static String[] VALID = new String[] 
+    { 
+	"codebase", 
+	"packages" , 
+	"trace", 
+	"debug", 
+	"recursive", 
+	"javac", 
+	"noclobber", 
+	"classname",
+	"skipdirs",
+	"outputbase"
+    }; 
+
+    final static String[] REQUIRED = new String[] 
+    { "codebase", "packages" , "trace", "debug" }; 
+
+    final static String[] ARGS = new String[]
+    { "codebase", "packages" , "trace", "debug", "classname", "outputbase" }; 
+
+    final static String EOL;
+
+    static
+    {
+	EOL = System.getProperty("line.separator");
+    }
+
+    static int      trace_level;
+    static boolean  debug;
+    static boolean  recursive;
+    static String   classname;
+    static boolean  clobber;
+    static Set      skipDirs;
+    
+    public synchronized static final void main(String[] argv)
+    {
+	String   codebase;
+	String   outputbase;
+	File[]   srcPkgDirs;
+
+	try
+	    {
+		ParsedCommandLine pcl = CommandLineUtils.parse( argv, "--", VALID, REQUIRED, ARGS);
+
+		//get and normalize the codebase
+		codebase = pcl.getSwitchArg("codebase");
+		codebase = platify( codebase );
+		if (! codebase.endsWith(File.separator))
+		    codebase += File.separator;
+
+		//get and normalize the outputbase
+		outputbase = pcl.getSwitchArg("outputbase");
+		if ( outputbase != null )
+		    {
+			outputbase = platify( outputbase );
+			if (! outputbase.endsWith(File.separator))
+			    outputbase += File.separator;
+		    }
+		else
+		    outputbase = codebase;
+
+		File outputBaseDir = new File( outputbase );
+		//System.err.println("outputBaseDir: " + outputBaseDir + "; exists? " +  outputBaseDir.exists());
+		if (outputBaseDir.exists())
+		    {
+			if (!outputBaseDir.isDirectory())
+			    {
+				//System.err.println("Output Base '" + outputBaseDir.getPath() + "' is not a directory!");
+				System.exit(-1);
+			    }
+			else if (!outputBaseDir.canWrite())
+			    {
+				System.err.println("Output Base '" + outputBaseDir.getPath() + "' is not writable!");
+				System.exit(-1);
+			    }
+		    }
+		else if (! outputBaseDir.mkdirs() )
+		    {
+			System.err.println("Output Base directory '" + outputBaseDir.getPath() + "' does not exist, and could not be created!");
+			System.exit(-1);
+		    }
+
+		//find existing package dirs 
+		String[] packages = StringTokenizerUtils.tokenizeToArray(pcl.getSwitchArg("packages"),", \t");
+		srcPkgDirs = new File[ packages.length ];
+		for(int i = 0, len = packages.length; i < len; ++i)
+		    srcPkgDirs[i] = new File(codebase + sepify(packages[i]));
+		
+		//find trace level
+		trace_level = Integer.parseInt( pcl.getSwitchArg("trace") );
+
+		//find debug
+		debug = BooleanUtils.parseBoolean( pcl.getSwitchArg("debug") );
+
+		//find classname, or use default
+		classname = pcl.getSwitchArg("classname");
+		if (classname == null)
+		    classname = "Debug";
+
+		//find recursive
+		recursive = pcl.includesSwitch("recursive");
+
+		//find clobber
+		clobber = !pcl.includesSwitch("noclobber");
+
+		//find skipDirs
+		String skipdirStr = pcl.getSwitchArg("skipdirs");
+		if (skipdirStr != null)
+		    {
+			String[] skipdirArray = StringTokenizerUtils.tokenizeToArray(skipdirStr, ", \t");
+			skipDirs = SetUtils.setFromArray( skipdirArray );
+		    }
+		else
+		    {
+			skipDirs = new HashSet();
+			skipDirs.add("CVS");
+		    }
+
+		if ( pcl.includesSwitch("javac") )
+		    System.err.println("autorecompilation of packages not yet implemented.");
+
+		for (int i = 0, len = srcPkgDirs.length; i < len; ++i)
+		    {
+			if (recursive)
+			    {
+				if (! recursivePrecheckPackages( codebase, srcPkgDirs[i], outputbase, outputBaseDir ))
+				    {
+					System.err.println("One or more of the specifies packages" +
+							   " could not be processed. Aborting." +
+							   " No files have been modified.");
+					System.exit(-1);
+				    }
+			    }
+			else
+			    {
+				if (! precheckPackage( codebase, srcPkgDirs[i], outputbase, outputBaseDir ))
+				    {
+					System.err.println("One or more of the specifies packages" +
+							   " could not be processed. Aborting." +
+							   " No files have been modified.");
+					System.exit(-1);
+				    }
+			    }
+		    }
+
+		for (int i = 0, len = srcPkgDirs.length; i < len; ++i)
+		    {
+			if (recursive)
+			    recursiveWriteDebugFiles( codebase, srcPkgDirs[i], outputbase, outputBaseDir );
+			else
+			    writeDebugFile( outputbase, srcPkgDirs[i] );
+		    }
+	    }
+	catch (Exception e)
+	    {
+		e.printStackTrace();
+		System.err.println();
+		usage();
+	    }
+    }
+
+    private static void usage()
+    {
+	System.err.println("java " + DebugGen.class.getName() + " \\");
+	System.err.println("\t--codebase=<directory under which packages live> \\  (no default)");
+	System.err.println("\t--packages=<comma separated list of packages>    \\  (no default)");
+	System.err.println("\t--debug=<true|false>                             \\  (no default)");
+	System.err.println("\t--trace=<an int between 0 and 10>                \\  (no default)");
+	System.err.println("\t--outputdir=<directory under which to generate>  \\  (defaults to same dir as codebase)");
+	System.err.println("\t--recursive                                      \\  (no args)");
+	System.err.println("\t--noclobber                                      \\  (no args)");
+	System.err.println("\t--classname=<class to generate>                  \\  (defaults to Debug)");
+	System.err.println("\t--skipdirs=<directories that should be skipped>  \\  (defaults to CVS)");
+    }
+
+    private static String ify(String str, char fromChar, char toChar)
+    {
+	if ( fromChar == toChar ) return str;
+
+	StringBuffer sb = new StringBuffer(str);
+	for (int i = 0, len = sb.length(); i < len; ++i)
+	    if (sb.charAt(i) == fromChar)
+		sb.setCharAt(i, toChar);
+	return sb.toString();
+    }
+
+    private static String platify( String str )
+    {
+	String out;
+	out = ify( str, '/', File.separatorChar );
+	out = ify( out, '\\', File.separatorChar );
+	out = ify( out, ':', File.separatorChar );
+	return out;
+    }
+
+    private static String dottify(String str)
+    { return ify(str, File.separatorChar, '.');}
+
+    private static String sepify(String str)
+    { return ify(str, '.', File.separatorChar);}
+
+    private static boolean recursivePrecheckPackages(String codebase, File srcPkgDir, String outputbase, File outputBaseDir) throws IOException
+    {
+	FileIterator fii = DirectoryDescentUtils.depthFirstEagerDescent( srcPkgDir );
+	while (fii.hasNext())
+	    {
+		File pkgDir = fii.nextFile();
+		if (! pkgDir.isDirectory() || skipDirs.contains(pkgDir.getName()))
+		    continue;
+
+		File outputDir = outputDir( codebase, pkgDir, outputbase, outputBaseDir );
+		if (! outputDir.exists() && !outputDir.mkdirs() )
+		    {
+			System.err.println( "Required output dir: '" + outputDir + 
+					    "' does not exist, and could not be created.");
+			return false;
+		    }
+		if (!precheckOutputPackageDir( outputDir ))
+		    return false;
+	    }		
+	return true;
+    }
+
+    private static File outputDir( String codebase, File srcPkgDir, String outputbase, File outputBaseDir )
+    {
+	//System.err.println("outputDir( " + codebase +", " + srcPkgDir + ", " + outputbase + ", " + outputBaseDir + " )");
+	if (codebase.equals(outputbase))
+	    return srcPkgDir;
+
+	String srcPath = srcPkgDir.getPath();
+	if (! srcPath.startsWith( codebase ))
+	    {
+		System.err.println(DebugGen.class.getName() + ": program bug. Source package path '" + srcPath + 
+				   "' does not begin with codebase '" + codebase + "'.");
+		System.exit(-1);
+	    }
+	return new File( outputBaseDir, srcPath.substring( codebase.length() ) );
+    }
+
+
+    private static boolean precheckPackage( String codebase, File srcPkgDir, String outputbase, File outputBaseDir ) throws IOException
+    { return precheckOutputPackageDir( outputDir(codebase, srcPkgDir, outputbase, outputBaseDir) ); }
+
+    private static boolean precheckOutputPackageDir(File dir) throws IOException
+    {
+	File outFile = new File( dir, classname + ".java" );
+	if (! dir.canWrite())
+	    {
+		System.err.println("File '" + outFile.getPath() + "' is not writable.");
+		return false;
+	    }
+	else if (!clobber && outFile.exists())
+	    {
+		System.err.println("File '" + outFile.getPath() + "' exists, and we are in noclobber mode.");
+		return false;
+	    }
+	else
+	    return true;
+    }
+
+    private static void recursiveWriteDebugFiles( String codebase, File srcPkgDir, String outputbase, File outputBaseDir ) throws IOException
+    {
+	FileIterator fii = DirectoryDescentUtils.depthFirstEagerDescent( outputDir( codebase, srcPkgDir, outputbase, outputBaseDir ) );
+	while (fii.hasNext())
+	    {
+		File pkgDir = fii.nextFile();
+		//System.err.println("pkgDir: " + pkgDir); 
+		if (! pkgDir.isDirectory() || skipDirs.contains(pkgDir.getName()))
+		    continue;
+		
+		writeDebugFile(outputbase, pkgDir);
+	    }		
+    }
+
+    private static void writeDebugFile(String outputbase, File pkgDir) throws IOException
+    {
+	//System.err.println("outputbase: " + outputbase + "; pkgDir: " + pkgDir);
+
+	File outFile = new File( pkgDir, classname + ".java" );
+	String pkg = dottify( pkgDir.getPath().substring( outputbase.length() ) );
+	System.err.println("Writing file: " + outFile.getPath());
+	Writer writer = null;
+	try
+	    {
+		writer = new OutputStreamWriter( 
+			   new BufferedOutputStream( 
+                             new FileOutputStream( outFile ) ), "UTF8" ); 
+		writer.write("/********************************************************************" + EOL);
+		writer.write(" * This class generated by " + DebugGen.class.getName() + EOL);
+		writer.write(" * and will probably be overwritten by the same! Edit at" + EOL);
+		writer.write(" * YOUR PERIL!!! Hahahahaha." + EOL);
+		writer.write(" ********************************************************************/" + EOL);
+		writer.write(EOL);
+		writer.write("package " + pkg + ';' + EOL);
+		writer.write(EOL);
+		writer.write("import com.mchange.v2.debug.DebugConstants;" + EOL);
+		writer.write(EOL);
+		writer.write("final class " + classname + " implements DebugConstants" + EOL);
+		writer.write("{" + EOL);
+		writer.write("\tfinal static boolean DEBUG = " + debug + ';' + EOL);
+		writer.write("\tfinal static int     TRACE = " + traceStr( trace_level ) + ';' + EOL);
+		writer.write(EOL);
+		writer.write("\tprivate " + classname + "()" + EOL);
+		writer.write("\t{}" + EOL);
+		writer.write("}" + EOL);
+		writer.write(EOL);
+		writer.flush();
+	    }
+	finally
+	    { WriterUtils.attemptClose( writer ); }
+    }
+
+    private static String traceStr( int trace )
+    {
+	if (trace == TRACE_NONE)
+	    return "TRACE_NONE";
+	else if (trace == TRACE_MED)
+	    return "TRACE_MED";
+	else if (trace == TRACE_MAX)
+	    return "TRACE_MAX";
+	else
+	    return String.valueOf(trace);
+    }
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/io/DirectoryDescentUtils.java b/relproj/debuggen/src/classes/com/mchange/v2/io/DirectoryDescentUtils.java
new file mode 100644
index 0000000..45c3f83
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/io/DirectoryDescentUtils.java
@@ -0,0 +1,130 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.io;
+
+import java.io.*;
+import java.util.*;
+
+public final class DirectoryDescentUtils
+{
+    /**
+     * @return FileIterator over all files and dierctories beneath root
+     */
+    public static FileIterator depthFirstEagerDescent(File root) 
+	throws IOException
+    { return depthFirstEagerDescent( root, null, false ); }
+
+    /**
+     * @return FileIterator over all files and directories beneath root that
+     *         match filter.
+     *
+     * @param  canonical file paths will be canonicalized if true
+     */
+    public static FileIterator depthFirstEagerDescent(File root, 
+						      FileFilter filter, 
+						      boolean canonical) 
+	throws IOException
+    { 
+	List list = new LinkedList();
+	Set  seenDirex = new HashSet();
+	depthFirstEagerDescend(root, filter, canonical, list, seenDirex);
+	return new IteratorFileIterator( list.iterator() );
+    }
+
+    public static void addSubtree( File root, FileFilter filter, boolean canonical, Collection addToMe ) throws IOException
+    {
+	Set  seenDirex = new HashSet();
+	depthFirstEagerDescend(root, filter, canonical, addToMe, seenDirex);
+    }
+
+    private static void depthFirstEagerDescend(File dir, FileFilter filter, boolean canonical, 
+					       Collection addToMe, Set seenDirex)
+	throws IOException
+    {
+	String canonicalPath = dir.getCanonicalPath();
+	if (! seenDirex.contains( canonicalPath ) )
+	    {
+		if ( filter == null || filter.accept( dir ) )
+		    addToMe.add( canonical ? new File( canonicalPath ) : dir );
+		seenDirex.add( canonicalPath );
+		String[] babies = dir.list();
+		for (int i = 0, len = babies.length; i < len; ++i)
+		    {
+			File baby = new File(dir, babies[i]);
+			if (baby.isDirectory())
+			    depthFirstEagerDescend(baby, filter, canonical, addToMe, seenDirex);
+			else
+			    if ( filter == null || filter.accept( baby ) )
+				addToMe.add( canonical ? baby.getCanonicalFile() : baby );
+		    }
+	    }
+    }
+
+    private static class IteratorFileIterator implements FileIterator
+    {
+	Iterator ii;
+	Object last;
+
+	IteratorFileIterator(Iterator ii)
+	{ this.ii = ii; }
+
+	public File nextFile() throws IOException
+	{ return (File) next(); }
+
+	public boolean hasNext() throws IOException
+	{ return ii.hasNext(); }
+
+	public Object next() throws IOException
+	{ return (last = ii.next()); }
+
+	public void remove() throws IOException
+	{
+	    if (last != null)
+		{
+		    ((File) last).delete();
+		    last = null;
+		}
+	    else
+		throw new IllegalStateException();
+	}
+
+	public void close() throws IOException
+	{}
+    }
+
+    private DirectoryDescentUtils()
+    {}
+
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		FileIterator fii = depthFirstEagerDescent( new File(argv[0]) );
+		while (fii.hasNext())
+		    System.err.println( fii.nextFile().getPath() );
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+}
diff --git a/relproj/debuggen/src/classes/com/mchange/v2/io/FileIterator.java b/relproj/debuggen/src/classes/com/mchange/v2/io/FileIterator.java
new file mode 100644
index 0000000..aebb117
--- /dev/null
+++ b/relproj/debuggen/src/classes/com/mchange/v2/io/FileIterator.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of debuggen v.0.1.0
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.NoSuchElementException;
+import com.mchange.v1.util.UIterator;
+
+public interface FileIterator extends UIterator
+{
+    public File nextFile() throws IOException;
+
+    public boolean hasNext() throws IOException;
+    public Object next() throws IOException;
+    public void remove() throws IOException;
+    public void close() throws IOException;
+
+    public final static FileIterator EMPTY_FILE_ITERATOR = new FileIterator()
+    {
+	public File nextFile() {throw new NoSuchElementException();}
+	public boolean hasNext() {return false;}
+	public Object next() {throw new NoSuchElementException();}
+	public void remove() {throw new IllegalStateException();}
+	public void close() {}
+    };
+}
diff --git a/relproj/debuggen/version.properties b/relproj/debuggen/version.properties
new file mode 100644
index 0000000..9ffb32d
--- /dev/null
+++ b/relproj/debuggen/version.properties
@@ -0,0 +1,3 @@
+#autogenerated -- do not edit!
+#Mon May 21 15:04:55 EDT 2007
+debuggen.version=0.1.0
diff --git a/src/classes/com/mchange/lang/ByteUtils.java b/src/classes/com/mchange/lang/ByteUtils.java
new file mode 100644
index 0000000..f354fe7
--- /dev/null
+++ b/src/classes/com/mchange/lang/ByteUtils.java
@@ -0,0 +1,111 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.lang;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.IOException;
+
+public final class ByteUtils
+{
+  public final static short UNSIGNED_MAX_VALUE = (Byte.MAX_VALUE * 2) + 1;
+
+  public static short toUnsigned(byte b)
+    {return (short) (b < 0 ? (UNSIGNED_MAX_VALUE + 1) + b : b);}
+
+  public static String toHexAscii(byte b)
+    {
+      StringWriter sw = new StringWriter(2);
+      addHexAscii(b, sw);
+      return sw.toString();
+    }
+
+  public static String toHexAscii(byte[] bytes)
+    {
+      int len = bytes.length;
+      StringWriter sw = new StringWriter(len * 2);
+      for (int i = 0; i < len; ++i)
+	addHexAscii(bytes[i], sw);
+      return sw.toString();
+    }
+
+  public static byte[] fromHexAscii(String s) throws NumberFormatException
+    {
+      try
+	{
+	  int len = s.length();
+	  if ((len % 2) != 0)
+	    throw new NumberFormatException("Hex ascii must be exactly two digits per byte.");
+	  
+	  int out_len = len / 2;
+	  byte[] out = new byte[out_len];
+	  int i = 0;
+	  StringReader sr = new StringReader(s); 
+	  while (i < out_len)
+	    {
+	      int val = (16 * fromHexDigit(sr.read())) + fromHexDigit(sr.read()); 
+	      out[i++] = (byte) val;
+	    }
+	  return out;
+	}
+      catch (IOException e)
+	{throw new InternalError("IOException reading from StringReader?!?!");}
+    }
+
+  static void addHexAscii(byte b, StringWriter sw)
+    {
+      short ub = toUnsigned(b);
+      int h1 = ub / 16;
+      int h2 = ub % 16;
+      sw.write(toHexDigit(h1));
+      sw.write(toHexDigit(h2));
+    }
+
+  private static int fromHexDigit(int c) throws NumberFormatException
+    {
+      if (c >= 0x30 && c < 0x3A)
+	return c - 0x30;
+      else if (c >= 0x41 && c < 0x47)
+	return c - 0x37;
+      else if (c >= 0x61 && c < 0x67)
+	return c - 0x57;
+      else 
+	throw new NumberFormatException('\'' + c + "' is not a valid hexadecimal digit.");
+    }
+
+  /* note: we do no arg. checking, because     */
+  /* we only ever call this from addHexAscii() */
+  /* above, and we are sure the args are okay  */
+  private static char toHexDigit(int h)
+    {
+	char out;
+	if (h <= 9) out = (char) (h + 0x30);
+	else out = (char) (h + 0x37);
+	//System.err.println(h + ": " + out);
+	return out;
+    }
+
+  private ByteUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/lang/PotentiallySecondary.java b/src/classes/com/mchange/lang/PotentiallySecondary.java
new file mode 100644
index 0000000..e5ed596
--- /dev/null
+++ b/src/classes/com/mchange/lang/PotentiallySecondary.java
@@ -0,0 +1,34 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.lang;
+
+/*
+ * An interface to be implemented by Throwable objects
+ * that may indicate a secondary problem, originated by the
+ * more primary, nested, throwable
+ */
+public interface PotentiallySecondary
+{
+    public Throwable getNestedThrowable();
+}
diff --git a/src/classes/com/mchange/lang/PotentiallySecondaryError.java b/src/classes/com/mchange/lang/PotentiallySecondaryError.java
new file mode 100644
index 0000000..71ff8b8
--- /dev/null
+++ b/src/classes/com/mchange/lang/PotentiallySecondaryError.java
@@ -0,0 +1,74 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.lang;
+
+import java.io.*;
+
+public class PotentiallySecondaryError extends Error implements PotentiallySecondary
+{
+    final static String NESTED_MSG = ">>>>>>>>>> NESTED THROWABLE >>>>>>>>";
+
+    Throwable nested;
+
+    public PotentiallySecondaryError(String msg, Throwable t)
+    {
+	super(msg);
+	this.nested = t;
+    }
+
+    public PotentiallySecondaryError(Throwable t)
+    {this("", t);}
+
+    public PotentiallySecondaryError(String msg)
+    {this(msg, null);}
+
+    public PotentiallySecondaryError()
+    {this("", null);}
+
+    public Throwable getNestedThrowable()
+    {return nested;}
+
+    public void printStackTrace(PrintWriter pw)
+    {
+	super.printStackTrace(pw);
+	if (nested != null)
+	    {
+		pw.println(NESTED_MSG);
+		nested.printStackTrace(pw);
+	    }
+    }
+
+    public void printStackTrace(PrintStream ps)
+    {
+	super.printStackTrace(ps);
+	if (nested != null)
+	    {
+		ps.println("NESTED_MSG");
+		nested.printStackTrace(ps);
+	    }
+    }
+
+    public void printStackTrace()
+    {printStackTrace(System.err);}
+}
diff --git a/src/classes/com/mchange/lang/PotentiallySecondaryException.java b/src/classes/com/mchange/lang/PotentiallySecondaryException.java
new file mode 100644
index 0000000..d86fb65
--- /dev/null
+++ b/src/classes/com/mchange/lang/PotentiallySecondaryException.java
@@ -0,0 +1,91 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.lang;
+
+import java.io.*;
+import com.mchange.v2.lang.VersionUtils;
+
+/**
+ * @deprecated jdk 1.4 mow includes this idea as part of the standard 
+ *             Throwable/Exception classes.
+ */            
+public class PotentiallySecondaryException extends Exception implements PotentiallySecondary
+{
+    final static String NESTED_MSG = ">>>>>>>>>> NESTED EXCEPTION >>>>>>>>";
+
+    Throwable nested;
+
+    public PotentiallySecondaryException(String msg, Throwable t)
+    {
+	super(msg);
+	this.nested = t;
+    }
+
+    public PotentiallySecondaryException(Throwable t)
+    {this("", t);}
+
+    public PotentiallySecondaryException(String msg)
+    {this(msg, null);}
+
+    public PotentiallySecondaryException()
+    {this("", null);}
+
+    public Throwable getNestedThrowable()
+    {return nested;}
+
+    private void setNested(Throwable t)
+    {
+	this.nested = t;
+	if ( VersionUtils.isAtLeastJavaVersion14() )
+	    this.initCause( t );
+    }
+
+    public void printStackTrace(PrintWriter pw)
+    {
+	super.printStackTrace(pw);
+	if ( !VersionUtils.isAtLeastJavaVersion14() && nested != null)
+	    {
+		pw.println(NESTED_MSG);
+		nested.printStackTrace(pw);
+	    }
+    }
+
+    public void printStackTrace(PrintStream ps)
+    {
+	super.printStackTrace(ps);
+	if ( !VersionUtils.isAtLeastJavaVersion14() && nested != null)
+	    {
+		ps.println("NESTED_MSG");
+		nested.printStackTrace(ps);
+	    }
+    }
+
+    public void printStackTrace()
+    {
+	if ( VersionUtils.isAtLeastJavaVersion14() )
+	    super.printStackTrace();
+	else
+	    this.printStackTrace(System.err);
+    }
+}
diff --git a/src/classes/com/mchange/lang/PotentiallySecondaryRuntimeException.java b/src/classes/com/mchange/lang/PotentiallySecondaryRuntimeException.java
new file mode 100644
index 0000000..bece9d4
--- /dev/null
+++ b/src/classes/com/mchange/lang/PotentiallySecondaryRuntimeException.java
@@ -0,0 +1,74 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.lang;
+
+import java.io.*;
+
+public class PotentiallySecondaryRuntimeException extends RuntimeException implements PotentiallySecondary
+{
+    final static String NESTED_MSG = ">>>>>>>>>> NESTED EXCEPTION >>>>>>>>";
+
+    Throwable nested;
+
+    public PotentiallySecondaryRuntimeException(String msg, Throwable t)
+    {
+	super(msg);
+	this.nested = t;
+    }
+
+    public PotentiallySecondaryRuntimeException(Throwable t)
+    {this("", t);}
+
+    public PotentiallySecondaryRuntimeException(String msg)
+    {this(msg, null);}
+
+    public PotentiallySecondaryRuntimeException()
+    {this("", null);}
+
+    public Throwable getNestedThrowable()
+    {return nested;}
+
+    public void printStackTrace(PrintWriter pw)
+    {
+	super.printStackTrace(pw);
+	if (nested != null)
+	    {
+		pw.println(NESTED_MSG);
+		nested.printStackTrace(pw);
+	    }
+    }
+
+    public void printStackTrace(PrintStream ps)
+    {
+	super.printStackTrace(ps);
+	if (nested != null)
+	    {
+		ps.println("NESTED_MSG");
+		nested.printStackTrace(ps);
+	    }
+    }
+
+    public void printStackTrace()
+    {printStackTrace(System.err);}
+}
diff --git a/src/classes/com/mchange/lang/ThrowableUtils.java b/src/classes/com/mchange/lang/ThrowableUtils.java
new file mode 100644
index 0000000..7d38d46
--- /dev/null
+++ b/src/classes/com/mchange/lang/ThrowableUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.lang;
+
+import java.io.*;
+
+public final class ThrowableUtils
+{
+  public static String extractStackTrace(Throwable t)
+    {
+      StringWriter me = new StringWriter();
+      PrintWriter pw = new PrintWriter(me);
+      t.printStackTrace(pw);
+      pw.flush();
+      return me.toString();
+    }
+
+    public static boolean isChecked(Throwable t)
+    { 
+	return 
+	    t instanceof Exception &&
+	    ! (t instanceof RuntimeException);
+    }
+
+    public static boolean isUnchecked(Throwable t)
+    { return ! isChecked( t ); }
+      
+    private ThrowableUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/util/AssertException.java b/src/classes/com/mchange/util/AssertException.java
new file mode 100644
index 0000000..d4ede9f
--- /dev/null
+++ b/src/classes/com/mchange/util/AssertException.java
@@ -0,0 +1,33 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.util;
+
+public class AssertException extends RuntimeException
+{
+  public AssertException(String message)
+    {super(message);}
+
+  public AssertException()
+    {super();}
+}
diff --git a/src/classes/com/mchange/v1/db/sql/ConnectionUtils.java b/src/classes/com/mchange/v1/db/sql/ConnectionUtils.java
new file mode 100644
index 0000000..c616638
--- /dev/null
+++ b/src/classes/com/mchange/v1/db/sql/ConnectionUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.db.sql;
+
+import java.sql.*;
+import com.mchange.v2.log.*;
+
+public final class ConnectionUtils
+{
+    private final static MLogger logger = MLog.getLogger( ConnectionUtils.class );
+
+    /** 
+     * @return false iff and Exception occurred while
+     *         trying to close this object.
+     */
+    public static boolean attemptClose(Connection con)
+    {
+	try 
+ 	    {
+ 		if (con != null) con.close();
+		//System.err.println("Connection [ " + con + " ] closed.");
+ 		return true;
+ 	    }
+        catch (SQLException e)
+	    {
+		//e.printStackTrace();
+		//System.err.println("Connection close FAILED.");
+
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "Connection close FAILED.", e );
+ 		return false;
+	    }
+    }
+
+    public static boolean attemptRollback(Connection con)
+    {
+        try 
+	    {
+		if (con != null) con.rollback();
+		return true;
+	    }
+        catch (SQLException e)
+            {
+		//e.printStackTrace();
+
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "Rollback FAILED.", e );
+		return false;
+	    }
+    }
+
+    private ConnectionUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v1/db/sql/ResultSetUtils.java b/src/classes/com/mchange/v1/db/sql/ResultSetUtils.java
new file mode 100644
index 0000000..ac07858
--- /dev/null
+++ b/src/classes/com/mchange/v1/db/sql/ResultSetUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.db.sql;
+
+import java.sql.*;
+import com.mchange.v2.log.*;
+
+public final class ResultSetUtils
+{
+    private final static MLogger logger = MLog.getLogger( ResultSetUtils.class );
+
+    /** 
+     * @return false iff and Exception occurred while
+     *         trying to close this object.
+     */
+    public static boolean attemptClose(ResultSet rs)
+    {
+        try 
+	    {
+		if (rs != null) rs.close();
+		return true;
+	    }
+        catch (SQLException e)
+            {
+		//e.printStackTrace();
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "ResultSet close FAILED.", e );
+		return false;
+	    }
+    }
+
+    private ResultSetUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v1/db/sql/StatementUtils.java b/src/classes/com/mchange/v1/db/sql/StatementUtils.java
new file mode 100644
index 0000000..ee40cf7
--- /dev/null
+++ b/src/classes/com/mchange/v1/db/sql/StatementUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.db.sql;
+
+import java.sql.*;
+import com.mchange.v2.log.*;
+
+public final class StatementUtils
+{
+    private final static MLogger logger = MLog.getLogger( StatementUtils.class );
+
+    /** 
+     * @return false iff and Exception occurred while
+     *         trying to close this object.
+     */
+    public static boolean attemptClose(Statement stmt)
+    {
+        try 
+	    {
+		if (stmt != null) stmt.close();
+		return true;
+	    }
+        catch (SQLException e)
+            {
+		//e.printStackTrace();
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "Statement close FAILED.", e );
+		return false;
+	    }
+    }
+
+    private StatementUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v1/identicator/IdHashKey.java b/src/classes/com/mchange/v1/identicator/IdHashKey.java
new file mode 100644
index 0000000..9fbb0f0
--- /dev/null
+++ b/src/classes/com/mchange/v1/identicator/IdHashKey.java
@@ -0,0 +1,41 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.identicator;
+
+abstract class IdHashKey
+{
+    Identicator id;
+    
+    public IdHashKey( Identicator id )
+    { this.id = id; }
+
+    public abstract Object getKeyObj();
+
+    public Identicator getIdenticator()
+    { return id; }
+
+    public abstract boolean equals(Object o);
+
+    public abstract int hashCode();
+}
diff --git a/src/classes/com/mchange/v1/identicator/IdHashMap.java b/src/classes/com/mchange/v1/identicator/IdHashMap.java
new file mode 100644
index 0000000..50ffb0b
--- /dev/null
+++ b/src/classes/com/mchange/v1/identicator/IdHashMap.java
@@ -0,0 +1,35 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.identicator;
+
+import java.util.*;
+
+public final class IdHashMap extends IdMap implements Map
+{
+    public IdHashMap(Identicator id)
+    { super ( new HashMap(), id ); }
+
+    protected IdHashKey createIdKey(Object o)
+    { return new StrongIdHashKey( o, id ); }
+}
diff --git a/src/classes/com/mchange/v1/identicator/IdMap.java b/src/classes/com/mchange/v1/identicator/IdMap.java
new file mode 100644
index 0000000..54f4de9
--- /dev/null
+++ b/src/classes/com/mchange/v1/identicator/IdMap.java
@@ -0,0 +1,131 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.identicator;
+
+import java.util.*;
+import com.mchange.v1.util.*;
+
+/*
+ * Implementation notes: many AbstractMap methods are written in
+ * terms of entrySet(). It is most important to get that right.
+ */
+abstract class IdMap extends AbstractMap implements Map
+{
+    Map         inner;
+    Identicator id;
+
+    protected IdMap(Map inner, Identicator id)
+    {
+	this.inner = inner;
+	this.id = id;
+    }
+    
+    public Object put(Object key, Object value) 
+    { return inner.put( createIdKey( key ), value ); }
+
+    public boolean containsKey(Object key)
+    { return inner.containsKey( createIdKey( key ) ); }
+
+    public Object get(Object key)
+    { return inner.get( createIdKey( key ) ); }
+
+    public Object remove(Object key)
+    { return inner.remove( createIdKey( key ) ); }
+
+    protected Object removeIdHashKey( IdHashKey idhk )
+    { return inner.remove( idhk ); }
+
+    public Set entrySet()
+    { return new UserEntrySet(); }
+
+    protected final Set internalEntrySet()
+    { return inner.entrySet(); }
+
+    protected abstract IdHashKey createIdKey(Object o);
+
+    protected final Entry createIdEntry(Object key, Object val)
+    { return new SimpleMapEntry( createIdKey( key ), val); }
+    
+    protected final Entry createIdEntry(Entry entry)
+    { return createIdEntry( entry.getKey(), entry.getValue() ); }
+
+    private final class UserEntrySet extends AbstractSet
+    {
+	Set innerEntries = inner.entrySet();
+	
+	public Iterator iterator()
+	{
+	    return new WrapperIterator(innerEntries.iterator(), true)
+		{
+		    protected Object transformObject(Object o)
+		    { return new UserEntry( (Entry) o ); }
+		};
+	}
+	
+	public int size()
+	{ return innerEntries.size(); }
+	
+	public boolean contains(Object o)
+	{ 
+	    if (o instanceof Entry)
+		{
+		    Entry entry = (Entry) o;
+		    return innerEntries.contains( createIdEntry( entry ) ); 
+		}
+	    else
+		return false;
+	}
+	
+	public boolean remove(Object o)
+	{
+	    if (o instanceof Entry)
+		{
+		    Entry entry = (Entry) o;
+		    return innerEntries.remove( createIdEntry( entry ) ); 
+		}
+	    else
+		return false;
+	}
+
+	public void clear()
+	{ inner.clear(); }
+    }
+
+    protected static class UserEntry extends AbstractMapEntry
+    {
+	private Entry innerEntry;
+
+	UserEntry(Entry innerEntry)
+	{ this.innerEntry = innerEntry; }
+
+	public final Object getKey()
+	{ return ((IdHashKey) innerEntry.getKey()).getKeyObj(); }
+
+	public final Object getValue()
+	{ return innerEntry.getValue(); }
+
+	public final Object setValue(Object value)
+	{ return innerEntry.setValue( value ); }
+    }
+}
diff --git a/src/classes/com/mchange/v1/identicator/IdWeakHashMap.java b/src/classes/com/mchange/v1/identicator/IdWeakHashMap.java
new file mode 100644
index 0000000..d9bdeec
--- /dev/null
+++ b/src/classes/com/mchange/v1/identicator/IdWeakHashMap.java
@@ -0,0 +1,252 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.identicator;
+
+import java.lang.ref.*;
+import java.util.*;
+import com.mchange.v1.util.WrapperIterator;
+
+/**
+ * IdWeakHashMap is NOT null-accepting!
+ */
+public final class IdWeakHashMap extends IdMap implements Map
+{
+    ReferenceQueue rq;
+
+    public IdWeakHashMap(Identicator id)
+    { 
+	super ( new HashMap(), id ); 
+	this.rq = new ReferenceQueue();
+    }
+
+    //all methods from Map interface
+    public int size()
+    {
+	// doing cleanCleared() afterwards, as with other methods
+	// would be just as "correct", as weak collections
+	// make no guarantees about when things disappear,
+	// but for size(), it feels a little more accurate
+	// this way.
+	cleanCleared();
+	return super.size();
+    }
+
+    public boolean isEmpty()
+    {
+	try
+	    { return super.isEmpty(); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public boolean containsKey(Object o)
+    {
+	try
+	    { return super.containsKey( o ); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public boolean containsValue(Object o)
+    {
+	try
+	    { return super.containsValue( o ); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public Object get(Object o)
+    {
+	try
+	    { return super.get( o ); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public Object put(Object k, Object v)
+    {
+	try
+	    { return super.put( k , v ); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public Object remove(Object o)
+    {
+	try
+	    { return super.remove( o ); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public void putAll(Map m)
+    {
+	try
+	    { super.putAll( m ); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public void clear()
+    {
+	try
+	    { super.clear(); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public Set keySet()
+    {
+	try
+	    { return super.keySet(); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public Collection values()
+    {
+	try
+	    { return super.values(); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    /*
+     * entrySet() is the basis of the implementation of the other
+     * Collection returning methods. Get this right and the rest 
+     * follow.
+     */
+    public Set entrySet()
+    {
+	try
+	    { return new WeakUserEntrySet(); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public boolean equals(Object o)
+    {
+	try
+	    { return super.equals( o ); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    public int hashCode()
+    {
+	try
+	    { return super.hashCode(); }
+	finally
+	    { cleanCleared(); }
+    }
+
+    //internal methods
+    protected IdHashKey createIdKey(Object o)
+    { return new WeakIdHashKey( o, id, rq ); }
+
+    private void cleanCleared()
+    {
+	WeakIdHashKey.Ref ref;
+	while ((ref = (WeakIdHashKey.Ref) rq.poll()) != null)
+	    this.removeIdHashKey( ref.getKey() );
+    }
+
+    private final class WeakUserEntrySet extends AbstractSet
+    {
+	Set innerEntries = internalEntrySet();
+	
+	public Iterator iterator()
+	{
+	    try
+		{
+		    return new WrapperIterator(innerEntries.iterator(), true)
+			{
+			    protected Object transformObject(Object o)
+			    {
+				Entry innerEntry = (Entry) o;
+				final Object userKey = ((IdHashKey) innerEntry.getKey()).getKeyObj();
+				if (userKey == null)
+				    return WrapperIterator.SKIP_TOKEN;
+				else
+				    return new UserEntry( innerEntry ) 
+					{ Object preventRefClear = userKey; };
+			    }
+			};
+		}
+	finally
+	    { cleanCleared(); }
+	}
+	
+	public int size()
+	{ 
+	    // doing cleanCleared() afterwards, as with other methods
+	    // would be just as "correct", as weak collections
+	    // make no guarantees about when things disappear,
+	    // but for size(), it feels a little more accurate
+	    // this way.
+	    cleanCleared();
+	    return innerEntries.size(); 
+	}
+	
+	public boolean contains(Object o)
+	{ 
+	    try
+		{
+		    if (o instanceof Entry)
+			{
+			    Entry entry = (Entry) o;
+			    return innerEntries.contains( createIdEntry( entry ) ); 
+			}
+		    else
+			return false;
+		}
+	    finally
+		{ cleanCleared(); }
+	}
+	
+	public boolean remove(Object o)
+	{
+	    try
+		{
+		    if (o instanceof Entry)
+			{
+			    Entry entry = (Entry) o;
+			    return innerEntries.remove( createIdEntry( entry ) ); 
+			}
+		    else
+			return false;
+		}
+	    finally
+		{ cleanCleared(); }
+	}
+
+	public void clear()
+	{
+	    try
+		{ inner.clear(); }
+	    finally
+		{ cleanCleared(); }
+	}
+    }
+}
diff --git a/src/classes/com/mchange/v1/identicator/Identicator.java b/src/classes/com/mchange/v1/identicator/Identicator.java
new file mode 100644
index 0000000..771d9ab
--- /dev/null
+++ b/src/classes/com/mchange/v1/identicator/Identicator.java
@@ -0,0 +1,34 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.identicator;
+
+/** 
+ *  Identicators should be immutable (sharable).
+ *  Cloned collections share identicators 
+ */
+public interface Identicator
+{
+    public boolean identical(Object a, Object b);
+    public int     hash(Object o);
+}
diff --git a/src/classes/com/mchange/v1/identicator/StrongIdHashKey.java b/src/classes/com/mchange/v1/identicator/StrongIdHashKey.java
new file mode 100644
index 0000000..e5aa188
--- /dev/null
+++ b/src/classes/com/mchange/v1/identicator/StrongIdHashKey.java
@@ -0,0 +1,52 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.identicator;
+
+// revisit equals() if ever made non-final
+
+final class StrongIdHashKey extends IdHashKey
+{
+    Object      keyObj;
+    
+    public StrongIdHashKey(Object keyObj, Identicator id)
+    {
+	super( id );
+	this.keyObj = keyObj;
+    }
+
+    public Object getKeyObj()
+    { return keyObj; }
+
+    public boolean equals(Object o)
+    {
+	//  fast type-exact match for final class
+	if (o instanceof StrongIdHashKey)
+	    return id.identical( keyObj, ((StrongIdHashKey) o).keyObj );
+	else
+	    return false;
+    }
+
+    public int hashCode()
+    { return id.hash( keyObj ); }
+}
diff --git a/src/classes/com/mchange/v1/identicator/WeakIdHashKey.java b/src/classes/com/mchange/v1/identicator/WeakIdHashKey.java
new file mode 100644
index 0000000..11a62c4
--- /dev/null
+++ b/src/classes/com/mchange/v1/identicator/WeakIdHashKey.java
@@ -0,0 +1,85 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.identicator;
+
+import java.lang.ref.*;
+
+// revisit equals() if ever made non-final
+
+final class WeakIdHashKey extends IdHashKey
+{
+    Ref keyRef;
+    int hash;
+
+    public WeakIdHashKey(Object keyObj, Identicator id, ReferenceQueue rq)
+    {
+	super( id );
+
+	if (keyObj == null)
+	    throw new UnsupportedOperationException("Collection does not accept nulls!");
+
+	this.keyRef = new Ref( keyObj, rq );
+	this.hash = id.hash( keyObj );
+    }
+
+    public Ref getInternalRef()
+    { return this.keyRef; }
+
+    public Object getKeyObj()
+    { return keyRef.get(); }
+
+    public boolean equals(Object o)
+    {
+	// fast type-exact match for final class
+	if (o instanceof WeakIdHashKey)
+	    {
+		WeakIdHashKey other = (WeakIdHashKey) o;
+		if (this.keyRef == other.keyRef)
+		    return true;
+		else
+		    {
+			Object myKeyObj = this.keyRef.get();
+			Object oKeyObj  = other.keyRef.get();
+			if (myKeyObj == null || oKeyObj == null)
+			    return false;
+			else
+			    return id.identical( myKeyObj, oKeyObj );
+		    }
+	    }
+	else
+	    return false;
+    }
+
+    public int hashCode()
+    { return hash; }
+
+    class Ref extends WeakReference
+    {
+	public Ref( Object referant, ReferenceQueue rq )
+	{ super( referant, rq ); }
+	
+	WeakIdHashKey getKey()
+	{ return WeakIdHashKey.this; }
+    }
+}
diff --git a/src/classes/com/mchange/v1/io/InputStreamUtils.java b/src/classes/com/mchange/v1/io/InputStreamUtils.java
new file mode 100644
index 0000000..b2c6371
--- /dev/null
+++ b/src/classes/com/mchange/v1/io/InputStreamUtils.java
@@ -0,0 +1,142 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.io;
+
+import java.io.*;
+import com.mchange.v2.log.*;
+
+public final class InputStreamUtils
+{
+    private final static MLogger logger = MLog.getLogger( InputStreamUtils.class );
+
+    public static boolean compare(InputStream is1, InputStream is2, long num_bytes) throws IOException
+    {
+	int b;
+	for (long num_read = 0; num_read < num_bytes; ++num_read)
+	    {
+		if ((b = is1.read()) != is2.read()) 
+		    return false;
+		else if (b < 0) //both EOF
+		    break;
+	    }
+	return true;
+    }
+
+    public static boolean compare(InputStream is1, InputStream is2) throws IOException
+    {
+	int b = 0;
+	while (b >= 0)
+	    if ((b = is1.read()) != is2.read()) 
+		return false;
+	return true;
+    }
+
+  public static byte[] getBytes(InputStream is, int max_len) throws IOException
+    {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream(max_len);
+      for(int i = 0, b = is.read(); b >= 0 && i < max_len; b = is.read(), ++i) 
+	baos.write(b);
+      return baos.toByteArray();
+    }
+
+  public static byte[] getBytes(InputStream is) throws IOException
+    {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      for(int b = is.read(); b >= 0; b = is.read()) baos.write(b);
+      return baos.toByteArray();
+    }
+
+  public static String getContentsAsString(InputStream is, String enc)
+    throws IOException, UnsupportedEncodingException
+    {return new String(getBytes(is), enc);}
+
+  public static String getContentsAsString(InputStream is)
+    throws IOException
+    {
+      try
+	{return getContentsAsString(is, System.getProperty("file.encoding", "8859_1"));}
+      catch (UnsupportedEncodingException e)
+	{
+	  throw new InternalError("You have no default character encoding, and " +
+				  "iso-8859-1 is unsupported?!?!");
+	}
+    }
+
+  public static String getContentsAsString(InputStream is, int max_len, String enc)
+    throws IOException, UnsupportedEncodingException
+    {return new String(getBytes(is, max_len), enc);}
+
+  public static String getContentsAsString(InputStream is, int max_len)
+    throws IOException
+    {
+      try
+	{return getContentsAsString(is, max_len, System.getProperty("file.encoding", "8859_1"));}
+      catch (UnsupportedEncodingException e)
+	{
+	  throw new InternalError("You have no default character encoding, and " +
+				  "iso-8859-1 is unsupported?!?!");
+	}
+    }
+
+  public static InputStream getEmptyInputStream()
+    {return EMPTY_ISTREAM;}
+
+  public static void attemptClose(InputStream is)
+    {
+	try
+	    {if (is != null) is.close();}
+	catch (IOException e)
+	    {
+		//e.printStackTrace();
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "InputStream close FAILED.", e );
+	    }
+    }
+
+  public static void skipFully(InputStream is, long num_bytes) throws EOFException, IOException
+    {
+      long num_skipped = 0;
+      while (num_skipped < num_bytes)
+	{
+	  long just_skipped = is.skip(num_bytes - num_skipped);
+	  if (just_skipped > 0)
+	    num_skipped += just_skipped;
+	  else
+	    {
+	      int test_byte = is.read();
+	      if (is.read() < 0)
+		throw new EOFException("Skipped only " + num_skipped + " bytes to end of file.");
+	      else
+		++num_skipped;
+	    }
+	}
+    }
+
+  /* Is it appropriate to treat this as a constant? Is it  */
+  /* in any discernable sense changed by read() operations */
+  private static InputStream EMPTY_ISTREAM = new ByteArrayInputStream(new byte[0]);
+
+  private InputStreamUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v1/io/OutputStreamUtils.java b/src/classes/com/mchange/v1/io/OutputStreamUtils.java
new file mode 100644
index 0000000..435b96f
--- /dev/null
+++ b/src/classes/com/mchange/v1/io/OutputStreamUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.io;
+
+import com.mchange.v2.log.*;
+import java.io.OutputStream;
+import java.io.IOException;
+
+public final class OutputStreamUtils
+{
+    private final static MLogger logger = MLog.getLogger( OutputStreamUtils.class );
+
+    public static void attemptClose(OutputStream os)
+    {
+      try
+	  {if (os != null) os.close();}
+      catch (IOException e)
+	  {
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "OutputStream close FAILED.", e );
+	      //e.printStackTrace();
+	  }
+    }
+
+  private OutputStreamUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v1/jvm/InternalNameUtils.java b/src/classes/com/mchange/v1/jvm/InternalNameUtils.java
new file mode 100644
index 0000000..0390b19
--- /dev/null
+++ b/src/classes/com/mchange/v1/jvm/InternalNameUtils.java
@@ -0,0 +1,211 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.jvm;
+
+public final class InternalNameUtils
+{
+    public static String dottifySlashesAndDollarSigns(String str)
+    { return _dottifySlashesAndDollarSigns( str ).toString(); }
+
+    public static String decodeType(String internalrep) throws TypeFormatException
+    { return _decodeType(internalrep).toString(); }
+
+    public static String decodeTypeList(String internalrep) throws TypeFormatException
+    { 
+	StringBuffer sb = new StringBuffer(64);
+	_decodeTypeList(internalrep, 0, sb);
+	return sb.toString();
+    }
+
+    public static boolean isPrimitive(char rep)
+    { 
+	return (rep == 'Z' ||
+		rep == 'B' ||
+		rep == 'C' ||
+		rep == 'S' ||
+		rep == 'I' ||
+		rep == 'J' ||
+		rep == 'F' ||
+		rep == 'D' ||
+		rep == 'V');
+    }
+
+    private static void _decodeTypeList(String typeList, int start_pos, StringBuffer appendTo) throws TypeFormatException
+    {
+	if (appendTo.length() != 0)
+	    appendTo.append(' ');
+
+	char c = typeList.charAt(start_pos);
+	if (isPrimitive(c))
+	    {
+		appendTo.append( _decodeType( typeList.substring(start_pos, start_pos + 1) ) );
+		++start_pos;
+	    }
+	else
+	    {
+		int stop_index;
+
+		if (c == '[')
+		    {
+			int finger = start_pos + 1;
+			while (typeList.charAt(finger) == '[')
+			    ++finger;
+			if (typeList.charAt(finger) == 'L')
+			    {
+				++finger;
+				while (typeList.charAt( finger ) != ';')
+				    ++finger;
+			    }
+			stop_index = finger;
+		    }
+		else
+		    {
+			stop_index = typeList.indexOf(';', start_pos);
+			if (stop_index < 0)
+			    throw new TypeFormatException(typeList.substring(start_pos) + " is neither a primitive nor semicolon terminated!");
+		    }
+
+		appendTo.append( _decodeType( typeList.substring( start_pos, (start_pos = stop_index + 1) ) ) );
+	    }
+	if (start_pos < typeList.length())
+	    {
+		appendTo.append(',');
+		_decodeTypeList(typeList, start_pos, appendTo);
+	    }
+    }
+
+    private static StringBuffer _decodeType(String type) throws TypeFormatException
+    {
+//  	System.err.println("_decodeType: " + type);
+
+	int array_level = 0;
+	StringBuffer out;
+
+	char c = type.charAt(0);
+	
+	switch (c)
+	    {
+	    case 'Z':
+		out = new StringBuffer("boolean");
+		break;
+	    case 'B':
+		out = new StringBuffer("byte");
+		break;
+	    case 'C':
+		out = new StringBuffer("char");
+		break;
+	    case 'S':
+		out = new StringBuffer("short");
+		break;
+	    case 'I':
+		out = new StringBuffer("int");
+		break;
+	    case 'J':
+		out = new StringBuffer("long");
+		break;
+	    case 'F':
+		out = new StringBuffer("float");
+		break;
+	    case 'D':
+		out = new StringBuffer("double");
+		break;
+	    case 'V':
+		out = new StringBuffer("void");
+		break;
+	    case '[':
+		++array_level;
+		out = _decodeType(type.substring(1));
+		break;
+	    case 'L':
+		out = _decodeSimpleClassType(type);
+		break;
+	    default:
+		throw new TypeFormatException(type + " is not a valid inernal type name.");
+	    }
+	for (int i = 0; i < array_level; ++i)
+	    out.append("[]");
+	return out;
+    }
+
+    private static StringBuffer _decodeSimpleClassType(String type) throws TypeFormatException
+    {
+	int len = type.length();
+	if (type.charAt(0) != 'L' || type.charAt( len - 1 ) != ';')
+	    throw new TypeFormatException(type + " is not a valid representation of a simple class type.");
+
+	return _dottifySlashesAndDollarSigns( type.substring(1, len - 1) );
+    }
+
+    private static StringBuffer _dottifySlashesAndDollarSigns(String s)
+    {
+	StringBuffer sb = new StringBuffer( s );
+	for (int i = 0, len = sb.length(); i < len; ++i)
+	    {
+		char c = sb.charAt(i);
+		if ( c == '/' || c == '$')
+		    sb.setCharAt(i, '.');
+	    }
+	return sb;
+    }
+
+    private InternalNameUtils()
+    {}
+
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		//System.out.println( decodeType( (new String[0]).getClass().getName() ) );
+		System.out.println(decodeTypeList(argv[0]));
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+//      public static String dottifySlashes(String name)
+//      {
+//  	StringBuffer sb = new StringBuffer(name);
+
+//  	int pos = name.indexOf('/', 0);
+//  	while (pos > 0)
+//  	    {
+//  		sb.setCharAt(pos, '.');
+//  		ps = name.indexOf('/', ++pos);
+//  	    }
+//  	return sb.toString();
+//      }
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/classes/com/mchange/v1/jvm/TypeFormatException.java b/src/classes/com/mchange/v1/jvm/TypeFormatException.java
new file mode 100644
index 0000000..a047e86
--- /dev/null
+++ b/src/classes/com/mchange/v1/jvm/TypeFormatException.java
@@ -0,0 +1,33 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.jvm;
+
+public class TypeFormatException extends Exception
+{
+    TypeFormatException()
+    { super(); }
+
+    TypeFormatException( String msg )
+    { super( msg ); }
+}
diff --git a/src/classes/com/mchange/v1/lang/AmbiguousClassNameException.java b/src/classes/com/mchange/v1/lang/AmbiguousClassNameException.java
new file mode 100644
index 0000000..9c23ad7
--- /dev/null
+++ b/src/classes/com/mchange/v1/lang/AmbiguousClassNameException.java
@@ -0,0 +1,34 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.lang;
+
+public class AmbiguousClassNameException extends Exception
+{
+    AmbiguousClassNameException(String simpleName, Class c1, Class c2)
+    { 
+	super( simpleName +
+	       " could refer either to " + c1.getName() + 
+	       " or " + c2.getName() );
+    }
+}
diff --git a/src/classes/com/mchange/v1/lang/BooleanUtils.java b/src/classes/com/mchange/v1/lang/BooleanUtils.java
new file mode 100644
index 0000000..7ee9bca
--- /dev/null
+++ b/src/classes/com/mchange/v1/lang/BooleanUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.lang;
+
+public final class BooleanUtils
+{
+    public static boolean parseBoolean(String str) throws IllegalArgumentException
+    {
+	if (str.equals("true"))
+	    return true;
+	else if (str.equals("false"))
+	    return false;
+	else
+	    throw new IllegalArgumentException("\"str\" is neither \"true\" nor \"false\".");
+    }
+
+    private BooleanUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v1/lang/ClassUtils.java b/src/classes/com/mchange/v1/lang/ClassUtils.java
new file mode 100644
index 0000000..1c32b35
--- /dev/null
+++ b/src/classes/com/mchange/v1/lang/ClassUtils.java
@@ -0,0 +1,217 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.lang;
+
+import java.util.*;
+import com.mchange.v1.jvm.*;
+
+public final class ClassUtils
+{
+    final static String[] EMPTY_SA = new String[0];
+
+    static Map primitivesToClasses;
+
+    static
+    {
+	HashMap tmp = new HashMap();
+	tmp.put( "boolean", boolean.class );
+	tmp.put( "int", int.class );
+	tmp.put( "char", char.class );
+	tmp.put( "short", short.class );
+	tmp.put( "int", int.class );
+	tmp.put( "long", long.class );
+	tmp.put( "float", float.class );
+	tmp.put( "double", double.class );
+	tmp.put( "void", void.class );
+
+	primitivesToClasses = Collections.unmodifiableMap( tmp );
+    }
+
+    public static Set allAssignableFrom(Class type)
+    {
+	Set out = new HashSet();
+
+	//type itself and superclasses (if any)
+	for (Class cl = type; cl != null; cl = cl.getSuperclass())
+	    out.add( cl );
+
+	//super interfaces (if any)
+	addSuperInterfacesToSet( type, out );
+	return out;
+    }
+
+    public static String simpleClassName(Class cl)
+    {
+	String scn;
+	int array_level = 0;
+	while (cl.isArray())
+	    {
+		++array_level;
+		cl = cl.getComponentType();
+	    }
+	scn = simpleClassName( cl.getName() );
+	if ( array_level > 0 )
+	    {
+		StringBuffer sb = new StringBuffer(16);
+		sb.append( scn );
+		for( int i = 0; i < array_level; ++i)
+		    sb.append("[]");
+		return sb.toString();
+	    }
+	else
+	    return scn;
+    }
+
+    private static String simpleClassName(String fqcn)
+    {
+       int pkgdot = fqcn.lastIndexOf('.');
+       if (pkgdot < 0)
+          return fqcn;
+       String scn = fqcn.substring(pkgdot + 1);
+       if (scn.indexOf('$') >= 0)
+          {
+             StringBuffer sb = new StringBuffer(scn);
+             for (int i = 0, len = sb.length(); i < len; ++i)
+             {
+                 if (sb.charAt(i) == '$')
+                   sb.setCharAt(i, '.');
+             }
+             return sb.toString();
+          }
+       else
+         return scn;
+    }
+
+    public static boolean isPrimitive(String typeStr)
+    { return (primitivesToClasses.get( typeStr ) != null); }
+
+    public static Class classForPrimitive(String typeStr)
+    { return (Class) primitivesToClasses.get( typeStr ); }
+    
+    public static Class forName(String fqcnOrPrimitive ) throws ClassNotFoundException
+    {
+        Class out = classForPrimitive( fqcnOrPrimitive );
+        if (out == null)
+            out = Class.forName( fqcnOrPrimitive );
+        return out;
+    }
+
+    public static Class forName( String fqOrSimple,  String[] importPkgs, String[] importClasses )
+	throws AmbiguousClassNameException, ClassNotFoundException
+    {
+	try
+	    { return Class.forName( fqOrSimple ); }
+	catch ( ClassNotFoundException e )
+	    { return classForSimpleName( fqOrSimple, importPkgs, importClasses ); }
+    }
+
+    public static Class classForSimpleName( String simpleName, String[] importPkgs, String[] importClasses )
+	throws AmbiguousClassNameException, ClassNotFoundException
+    {
+	Set checkSet = new HashSet();
+	Class out = classForPrimitive( simpleName );
+
+	if (out == null)
+	    {
+		if (importPkgs == null)
+		    importPkgs = EMPTY_SA;
+		
+		if (importClasses == null)
+		    importClasses = EMPTY_SA;
+		
+		for (int i = 0, len = importClasses.length; i < len; ++i)
+		    {
+			String importSimpleName = fqcnLastElement( importClasses[i] );
+			if (! checkSet.add( importSimpleName ) )
+			    throw new IllegalArgumentException("Duplicate imported classes: " + 
+							       importSimpleName);
+			if ( simpleName.equals( importSimpleName ) ) 
+			    //we won't duplicate assign. we'd have caught it above
+			    out = Class.forName( importClasses[i] );
+		    }
+		if (out == null)
+		    {
+			try { out = Class.forName("java.lang." + simpleName); }
+			catch (ClassNotFoundException e)
+			    { /* just means we haven't found it yet */ }
+
+			for (int i = 0, len = importPkgs.length; i < len; ++i)
+			    {
+				try
+				    {
+					String tryClass = importPkgs[i] + '.' + simpleName;
+					Class test = Class.forName( tryClass );
+					if ( out == null )
+					    out = test;
+					else
+					    throw new AmbiguousClassNameException( simpleName, out, test );
+				    }
+				catch (ClassNotFoundException e)
+				    { /* just means we haven't found it yet */ }
+			    }
+		    }
+	    }
+	if (out == null)
+	    throw new ClassNotFoundException( "Could not find a class whose unqualified name is \042" +
+					      simpleName + "\042 with the imports supplied. Import packages are " +
+					      Arrays.asList( importPkgs ) + "; class imports are " +
+					      Arrays.asList( importClasses ) );
+	else
+	    return out;
+    }
+
+    public static String resolvableTypeName( Class type, String[] importPkgs, String[] importClasses )
+	throws ClassNotFoundException
+    {
+	String simpleName = simpleClassName( type );
+	try
+	    { classForSimpleName( simpleName, importPkgs, importClasses ); }
+	catch ( AmbiguousClassNameException e )
+	    { return type.getName(); }
+	return simpleName;
+    }
+
+    public static String fqcnLastElement(String fqcn)
+    {
+       int pkgdot = fqcn.lastIndexOf('.');
+       if (pkgdot < 0)
+          return fqcn;
+       return fqcn.substring(pkgdot + 1);
+    }
+
+
+    /* does not add type itself, only its superinterfaces */
+    private static void addSuperInterfacesToSet(Class type, Set set)
+    {
+	Class[] ifaces = type.getInterfaces();
+	for (int i = 0, len = ifaces.length; i < len; ++i)
+	    {
+		set.add( ifaces[i] );
+		addSuperInterfacesToSet( ifaces[i], set );
+	    }
+    }
+
+    private ClassUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v1/lang/GentleThread.java b/src/classes/com/mchange/v1/lang/GentleThread.java
new file mode 100644
index 0000000..fd714ff
--- /dev/null
+++ b/src/classes/com/mchange/v1/lang/GentleThread.java
@@ -0,0 +1,98 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.lang;
+
+/**
+ * an abstract Thread class that provides
+ * utilities for easily defining Threads with
+ * safe versions of the deprecated thread
+ * methods stop(), resume(), and start()
+ */
+public abstract class GentleThread extends Thread
+{
+    boolean should_stop = false;
+    boolean should_suspend = false;
+    
+    public GentleThread()
+    { super(); }
+
+    public GentleThread(String name)
+    { super( name ); }
+
+    public abstract void run();
+
+    /**
+     * a safe method for stopping properly implemented GentleThreads
+     */
+    public synchronized void gentleStop()
+    {should_stop = true;}
+
+    /**
+     * a safe method for suspending properly implemented GentleThreads
+     */
+    public synchronized void gentleSuspend()
+    {should_suspend = true;}
+
+    /**
+     * a safe method for resuming properly implemented GentleThreads
+     */
+    public synchronized void gentleResume()
+    {
+	should_suspend = false;
+	this.notifyAll();
+    }
+
+    /**
+     * tests whether the thread should stop.
+     * Subclasses should call this method periodically in 
+     * their run method, and return from run() is the
+     * method returns true.
+     */
+    protected synchronized boolean shouldStop()
+    {return should_stop;}
+
+    /**
+     * tests whether the thread should suspend.
+     * Subclasses rarely call this method directly,
+     * and should call allowSuspend() periodically
+     * instead.
+     *
+     * @see #allowSuspend
+     */
+    protected synchronized boolean shouldSuspend()
+    {return should_suspend;}
+
+    /**
+     * tests whether the thread should suspend,
+     * and causes to the thread to pause if appropriate.
+     * Subclasses should call this method periodically
+     * in their run method to, um, allow suspension.
+     * Threads paused by allowSuspend() will be properly
+     * awoken by gentleResume()
+     *
+     * @see #gentleResume
+     */
+    protected synchronized void allowSuspend() throws InterruptedException 
+    {while (should_suspend) this.wait();}
+}
diff --git a/src/classes/com/mchange/v1/lang/NullUtils.java b/src/classes/com/mchange/v1/lang/NullUtils.java
new file mode 100644
index 0000000..55c3081
--- /dev/null
+++ b/src/classes/com/mchange/v1/lang/NullUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.lang;
+
+/**
+ * @deprecated use com.mchange.v2.ObjectUtils.eqOrBothNull()
+ */
+public final class NullUtils
+{
+    public static boolean equalsOrBothNull(Object a, Object b)
+    {
+	if (a == b)
+	    return true;
+	else if (a == null)
+	    return false;
+	else
+	    return a.equals( b );
+    }
+
+    private NullUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v1/util/AbstractMapEntry.java b/src/classes/com/mchange/v1/util/AbstractMapEntry.java
new file mode 100644
index 0000000..7e91019
--- /dev/null
+++ b/src/classes/com/mchange/v1/util/AbstractMapEntry.java
@@ -0,0 +1,56 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import java.util.Map;
+import com.mchange.v2.lang.ObjectUtils;
+
+public abstract class AbstractMapEntry implements Map.Entry
+{
+    public abstract Object getKey();
+
+    public abstract Object getValue();
+
+    public abstract Object setValue(Object value);
+
+    public boolean equals(Object o)
+    {
+	if (o instanceof Map.Entry)
+	    {
+		Map.Entry other = (Map.Entry) o;
+		return
+		    ObjectUtils.eqOrBothNull( this.getKey(), other.getKey() ) &&
+		    ObjectUtils.eqOrBothNull( this.getValue(), other.getValue() );
+	    }
+	else 
+	    return false;
+    }
+
+    public int hashCode()
+    {
+	return 
+	    (this.getKey()   == null ? 0 : this.getKey().hashCode()) ^
+	    (this.getValue() == null ? 0 : this.getValue().hashCode());
+    }
+}
diff --git a/src/classes/com/mchange/v1/util/ArrayUtils.java b/src/classes/com/mchange/v1/util/ArrayUtils.java
new file mode 100644
index 0000000..4bee927
--- /dev/null
+++ b/src/classes/com/mchange/v1/util/ArrayUtils.java
@@ -0,0 +1,300 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import com.mchange.v2.lang.ObjectUtils;
+
+public final class ArrayUtils
+{
+    /**
+     *  The array may contain nulls, but <TT>o</TT>
+     *  must be non-null.
+     */
+    public static int indexOf(Object[] array, Object o)
+    {
+    for (int i = 0, len = array.length; i < len; ++i)
+        if (o.equals(array[i])) return i;
+    return -1;
+    }
+    
+    public static int identityIndexOf(Object[] array, Object o)
+    {
+    for (int i = 0, len = array.length; i < len; ++i)
+        if (o == array[i]) return i;
+    return -1;
+    }
+    
+    public static boolean startsWith( byte[] checkMe, byte[] maybePrefix )
+    {
+    int cm_len = checkMe.length;
+    int mp_len = maybePrefix.length;
+    if (cm_len < mp_len)
+        return false;
+    for (int i = 0; i < mp_len; ++i)
+        if (checkMe[i] != maybePrefix[i])
+        return false;
+    return true;
+    }
+    
+    /**
+     * returns a hash-code for an array consistent with Arrays.equals( ... )
+     */
+    public static int hashArray(Object[] oo)
+    {
+	int len = oo.length;
+	int out = len;
+  	for (int i = 0; i < len; ++i)
+  	    {
+  		//we rotate the bits of the element hashes
+  		//around so that the hash has some loaction
+  		//dependency
+  		int elem_hash = ObjectUtils.hashOrZero(oo[i]);
+  		int rot = i % 32;
+  		int rot_hash = elem_hash >>> rot;
+  		rot_hash |= elem_hash << (32 - rot);
+  		out ^= rot_hash;
+  	    }
+	return out;
+    }
+
+    /**
+     * returns a hash-code for an array consistent with Arrays.equals( ... )
+     */
+    public static int hashArray(int[] ii)
+    {
+	int len = ii.length;
+	int out = len;
+  	for (int i = 0; i < len; ++i)
+  	    {
+  		//we rotate the bits of the element hashes
+  		//around so that the hash has some loaction
+  		//dependency
+  		int elem_hash = ii[i];
+  		int rot = i % 32;
+  		int rot_hash = elem_hash >>> rot;
+  		rot_hash |= elem_hash << (32 - rot);
+  		out ^= rot_hash;
+  	    }
+	return out;
+    }
+
+    public static int hashOrZeroArray(Object[] oo)
+    { return (oo == null ? 0 : hashArray(oo)); }
+
+    public static int hashOrZeroArray(int[] ii)
+    { return (ii == null ? 0 : hashArray(ii)); }
+
+    /**
+     * @deprecated use the various toString(T[] methods)
+     */
+    public static String stringifyContents(Object[] array)
+    {
+	StringBuffer sb = new StringBuffer();
+	sb.append("[ ");
+	for (int i = 0, len = array.length; i < len; ++i)
+	    {
+		if (i != 0)
+		    sb.append(", ");
+		sb.append( array[i].toString() );
+	    }
+	sb.append(" ]");
+	return sb.toString();
+    }
+    
+    //these methods are obsoleted by Arrays.toString() in jdk1.5, but
+    //for libs that support older VMs...
+    private static String toString(String[] strings, int guessed_len)
+    {
+        StringBuffer sb = new StringBuffer( guessed_len );
+        boolean first = true;
+        sb.append('[');
+        for (int i = 0, len = strings.length; i < len; ++i)
+        {
+            if (first)
+                first = false;
+            else
+                sb.append(',');
+            sb.append( strings[i] );
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+    
+    public static String toString(boolean[] arr)
+    {
+        String[] strings = new String[arr.length];
+        int chars = 0;
+        for(int i = 0, len = arr.length; i < len; ++i)
+        {
+            String str = String.valueOf(arr[i]);
+            chars += str.length();
+            strings[i] = str;
+        }
+        return toString(strings, chars + arr.length + 1);
+    }
+    
+    public static String toString(byte[] arr)
+    {
+        String[] strings = new String[arr.length];
+        int chars = 0;
+        for(int i = 0, len = arr.length; i < len; ++i)
+        {
+            String str = String.valueOf(arr[i]);
+            chars += str.length();
+            strings[i] = str;
+        }
+        return toString(strings, chars + arr.length + 1);
+    }
+    
+    public static String toString(char[] arr)
+    {
+        String[] strings = new String[arr.length];
+        int chars = 0;
+        for(int i = 0, len = arr.length; i < len; ++i)
+        {
+            String str = String.valueOf(arr[i]);
+            chars += str.length();
+            strings[i] = str;
+        }
+        return toString(strings, chars + arr.length + 1);
+    }
+
+    public static String toString(short[] arr)
+    {
+        String[] strings = new String[arr.length];
+        int chars = 0;
+        for(int i = 0, len = arr.length; i < len; ++i)
+        {
+            String str = String.valueOf(arr[i]);
+            chars += str.length();
+            strings[i] = str;
+        }
+        return toString(strings, chars + arr.length + 1);
+    }
+
+    public static String toString(int[] arr)
+    {
+        String[] strings = new String[arr.length];
+        int chars = 0;
+        for(int i = 0, len = arr.length; i < len; ++i)
+        {
+            String str = String.valueOf(arr[i]);
+            chars += str.length();
+            strings[i] = str;
+        }
+        return toString(strings, chars + arr.length + 1);
+    }
+    
+    public static String toString(long[] arr)
+    {
+        String[] strings = new String[arr.length];
+        int chars = 0;
+        for(int i = 0, len = arr.length; i < len; ++i)
+        {
+            String str = String.valueOf(arr[i]);
+            chars += str.length();
+            strings[i] = str;
+        }
+        return toString(strings, chars + arr.length + 1);
+    }
+    
+   public static String toString(float[] arr)
+    {
+        String[] strings = new String[arr.length];
+        int chars = 0;
+        for(int i = 0, len = arr.length; i < len; ++i)
+        {
+            String str = String.valueOf(arr[i]);
+            chars += str.length();
+            strings[i] = str;
+        }
+        return toString(strings, chars + arr.length + 1);
+    }
+    
+    public static String toString(double[] arr)
+    {
+        String[] strings = new String[arr.length];
+        int chars = 0;
+        for(int i = 0, len = arr.length; i < len; ++i)
+        {
+            String str = String.valueOf(arr[i]);
+            chars += str.length();
+            strings[i] = str;
+        }
+        return toString(strings, chars + arr.length + 1);
+    }
+
+    public static String toString(Object[] arr)
+    {
+        String[] strings = new String[arr.length];
+        int chars = 0;
+        for(int i = 0, len = arr.length; i < len; ++i)
+        {
+            String str;
+            Object o = arr[i];
+            if (o instanceof Object[])
+                str = toString((Object[]) o);
+            else if (o instanceof double[])
+                str = toString((double[]) o);
+            else if (o instanceof float[])
+                str = toString((float[]) o);
+            else if (o instanceof long[])
+                str = toString((long[]) o);
+            else if (o instanceof int[])
+                str = toString((int[]) o);
+            else if (o instanceof short[])
+                str = toString((short[]) o);
+            else if (o instanceof char[])
+                str = toString((char[]) o);
+            else if (o instanceof byte[])
+                str = toString((byte[]) o);
+            else if (o instanceof boolean[])
+                str = toString((boolean[]) o);
+            else
+                str = String.valueOf(arr[i]);
+            chars += str.length();
+            strings[i] = str;
+        }
+        return toString(strings, chars + arr.length + 1);
+    }
+
+    
+    private ArrayUtils()
+    {}
+    
+    /*
+    public static void main(String[] argv)
+    {
+        int[] is = {1,2,3,4};
+        String[] ss = {"Hello", "There"};
+        Object[] os = {"Poop", is, ss, new Thread()};
+        
+        System.out.println( toString(is) );
+        System.out.println( toString(ss) );
+        System.out.println( toString(os) );
+    }
+    */
+}    
+
+
diff --git a/src/classes/com/mchange/v1/util/ClosableResource.java b/src/classes/com/mchange/v1/util/ClosableResource.java
new file mode 100644
index 0000000..a79c44a
--- /dev/null
+++ b/src/classes/com/mchange/v1/util/ClosableResource.java
@@ -0,0 +1,40 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+/**
+ * An interface intended to be shared by the many sorts
+ * of objects that offer some kind of close method to
+ * cleanup resources they might be using. (I wish Sun
+ * had defined, and used, such an interface in the standard
+ * APIs.
+ */
+public interface ClosableResource
+{
+    /**
+     * forces the release of any resources that might be
+     * associated with this object.
+     */
+    public void close() throws Exception;
+}
diff --git a/src/classes/com/mchange/v1/util/ClosableResourceUtils.java b/src/classes/com/mchange/v1/util/ClosableResourceUtils.java
new file mode 100644
index 0000000..a253914
--- /dev/null
+++ b/src/classes/com/mchange/v1/util/ClosableResourceUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import com.mchange.v2.log.*;
+
+public final class ClosableResourceUtils
+{
+    private final static MLogger logger = MLog.getLogger( ClosableResourceUtils.class );
+
+    /**
+     * attempts to close the specified resource,
+     * logging any exception or failure, but allowing
+     * control flow to proceed normally regardless.
+     */
+    public static Exception attemptClose(ClosableResource cr)
+    {
+	try
+	    {
+		if (cr != null) cr.close();
+		return null;
+	    }
+	catch (Exception e)
+	    {
+		//e.printStackTrace();
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "CloseableResource close FAILED.", e );
+		return e;
+	    }
+    }
+
+    private ClosableResourceUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v1/util/DebugUtils.java b/src/classes/com/mchange/v1/util/DebugUtils.java
new file mode 100644
index 0000000..984a30d
--- /dev/null
+++ b/src/classes/com/mchange/v1/util/DebugUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import com.mchange.util.AssertException;
+
+public class DebugUtils
+{
+    private DebugUtils() {}
+    
+    public static void myAssert(boolean bool)
+    {if (!bool) throw new AssertException();}
+    
+    public static void myAssert(boolean bool, String message)
+    {if (!bool) throw new AssertException(message);}
+}
+
diff --git a/src/classes/com/mchange/v1/util/SimpleMapEntry.java b/src/classes/com/mchange/v1/util/SimpleMapEntry.java
new file mode 100644
index 0000000..2a04667
--- /dev/null
+++ b/src/classes/com/mchange/v1/util/SimpleMapEntry.java
@@ -0,0 +1,51 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import java.util.Map;
+
+public class SimpleMapEntry extends AbstractMapEntry implements Map.Entry
+{
+    Object key;
+    Object value;
+
+    public SimpleMapEntry(Object key, Object value)
+    {
+	this.key = key;
+	this.value = value;
+    }
+
+    public Object getKey()
+    { return key; }
+
+    public Object getValue()
+    { return value; }
+
+    public Object setValue(Object value)
+    {
+	Object old = value;
+	this.value = value;
+	return old;
+    }
+}
diff --git a/src/classes/com/mchange/v1/util/StringTokenizerUtils.java b/src/classes/com/mchange/v1/util/StringTokenizerUtils.java
new file mode 100644
index 0000000..007cbb7
--- /dev/null
+++ b/src/classes/com/mchange/v1/util/StringTokenizerUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import java.util.StringTokenizer;
+
+public final class StringTokenizerUtils
+{
+    public static String[] tokenizeToArray(String str, String delim, boolean returntokens)
+    {
+	StringTokenizer st = new StringTokenizer(str, delim, returntokens);
+	String[] strings = new String[st.countTokens()];
+	for (int i = 0; st.hasMoreTokens(); ++i)
+	   strings[i] = st.nextToken();
+	return strings;
+    }
+
+    public static String[] tokenizeToArray(String str, String delim)
+    {return tokenizeToArray(str, delim, false);}
+
+    public static String[] tokenizeToArray(String str)
+    {return tokenizeToArray(str, " \t\r\n");}
+}
+
+
+
+
diff --git a/src/classes/com/mchange/v1/util/WrapperIterator.java b/src/classes/com/mchange/v1/util/WrapperIterator.java
new file mode 100644
index 0000000..a894d5a
--- /dev/null
+++ b/src/classes/com/mchange/v1/util/WrapperIterator.java
@@ -0,0 +1,115 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.util;
+
+import java.util.*;
+import com.mchange.v1.util.DebugUtils;
+
+/**
+ *  This implementation does not yet support removes once hasNext() has
+ *  been called... will add if necessary.
+ */
+public abstract class WrapperIterator implements Iterator
+{
+    protected final static Object SKIP_TOKEN = new Object();
+
+    final static boolean DEBUG = true;
+
+    Iterator inner;
+    boolean  supports_remove;
+    Object   lastOut = null;
+    Object   nextOut = SKIP_TOKEN;
+    
+    public WrapperIterator(Iterator inner, boolean supports_remove)
+    { 
+	this.inner = inner; 
+	this.supports_remove = supports_remove;
+    }
+
+    public WrapperIterator(Iterator inner)
+    { this( inner, false ); }
+
+    public boolean hasNext()
+    {
+	findNext();
+	return nextOut != SKIP_TOKEN; 
+    }
+
+    private void findNext()
+    {
+	if (nextOut == SKIP_TOKEN)
+	    {
+		while (inner.hasNext() && nextOut == SKIP_TOKEN)
+		    this.nextOut = transformObject( inner.next() );
+	    }
+    }
+
+    public Object next()
+    {
+	findNext();
+	if (nextOut != SKIP_TOKEN)
+	    {
+		lastOut = nextOut;
+		nextOut = SKIP_TOKEN;
+	    }
+	else
+	    throw new NoSuchElementException();
+
+	//postcondition
+	if (DEBUG)
+	    DebugUtils.myAssert( nextOut == SKIP_TOKEN && lastOut != SKIP_TOKEN );
+	return lastOut;
+    }
+    
+    public void remove()
+    { 
+	if (supports_remove)
+	    {
+		if (nextOut != SKIP_TOKEN)
+		    throw new UnsupportedOperationException(this.getClass().getName() +
+							    " cannot support remove after" +
+							    " hasNext() has been called!");
+		if (lastOut != SKIP_TOKEN)
+		    inner.remove();
+		else
+		    throw new NoSuchElementException();
+	    }
+	else
+	    throw new UnsupportedOperationException(); 
+    }
+
+    /**
+     * return SKIP_TOKEN to indicate an object should be
+     * skipped, i.e., not exposed as part of the iterator.
+     * (we don't use null, because we want to support iterators
+     * over null-accepting Collections.)
+     */
+    protected abstract Object transformObject(Object o);
+}
+
+
+
+
+
+
diff --git a/src/classes/com/mchange/v1/xml/DomParseUtils.java b/src/classes/com/mchange/v1/xml/DomParseUtils.java
new file mode 100644
index 0000000..e7c1e0d
--- /dev/null
+++ b/src/classes/com/mchange/v1/xml/DomParseUtils.java
@@ -0,0 +1,179 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v1.xml;
+
+import java.util.*;
+import org.xml.sax.*;
+import org.w3c.dom.*;
+import com.mchange.v1.util.DebugUtils;
+
+public final class DomParseUtils
+{
+    final static boolean DEBUG = true;
+
+    /**
+     * @return null if child doesn't exist.
+     */
+    public static String allTextFromUniqueChild(Element elem, String childTagName)
+	throws DOMException
+    { return allTextFromUniqueChild( elem, childTagName, false ); }
+
+    /**
+     * @return null if child doesn't exist.
+     */
+	       public static String allTextFromUniqueChild(Element elem, String childTagName, boolean trim)
+	throws DOMException
+    {
+	Element uniqueChild = uniqueChildByTagName( elem, childTagName );
+	if (uniqueChild == null)
+	    return null;
+	else
+	    return DomParseUtils.allTextFromElement( uniqueChild, trim );
+    }
+
+    public static Element uniqueChild(Element elem, String childTagName) throws DOMException
+    { return uniqueChildByTagName( elem, childTagName); }
+
+    /**
+     * @deprecated use uniqueChild(Element elem, String childTagName)
+     */
+    public static Element uniqueChildByTagName(Element elem, String childTagName) throws DOMException
+    {
+	NodeList nl = elem.getElementsByTagName(childTagName);
+	int len = nl.getLength();
+	if (DEBUG)
+	    DebugUtils.myAssert( len <= 1 ,
+				 "There is more than one (" + len + ") child with tag name: " + 
+				 childTagName + "!!!" );
+	return (len == 1 ? (Element) nl.item( 0 ) : null);
+    }
+
+    public static String allText(Element elem) throws DOMException
+    { return allTextFromElement( elem ); }
+
+    public static String allText(Element elem, boolean trim) throws DOMException
+    { return allTextFromElement( elem, trim ); }
+
+    /** @deprecated use allText(Element elem) */
+    public static String allTextFromElement(Element elem) throws DOMException
+    { return allTextFromElement( elem, false); }
+
+    /** @deprecated use allText(Element elem, boolean trim) */
+    public static String allTextFromElement(Element elem, boolean trim) throws DOMException
+    {
+	StringBuffer textBuf = new StringBuffer();
+	NodeList nl = elem.getChildNodes();
+	for (int j = 0, len = nl.getLength(); j < len; ++j)
+	    {
+		Node node = nl.item(j);
+		if (node instanceof Text) //includes Text and CDATA!
+		    textBuf.append(node.getNodeValue());
+	    }
+	String out = textBuf.toString();
+	return ( trim ? out.trim() : out );
+    }
+
+    public static String[] allTextFromImmediateChildElements( Element parent, String tagName ) 
+	throws DOMException
+    { return allTextFromImmediateChildElements( parent, tagName, false ); }
+
+    public static String[] allTextFromImmediateChildElements( Element parent, String tagName, boolean trim ) 
+	throws DOMException
+    {
+	NodeList nl = immediateChildElementsByTagName( parent, tagName );
+	int len = nl.getLength();
+	String[] out = new String[ len ];
+	for (int i = 0; i < len; ++i)
+	    out[i] = allText( (Element) nl.item(i), trim );
+	return out;
+    }
+
+
+    public static NodeList immediateChildElementsByTagName( Element parent, String tagName )
+	throws DOMException
+    { return getImmediateChildElementsByTagName( parent, tagName ); }
+
+    /**
+     * @deprecated use immediateChildrenByTagName( Element parent, String tagName )
+     */
+    public static NodeList getImmediateChildElementsByTagName( Element parent, String tagName )
+	throws DOMException
+    {
+	final List nodes = new ArrayList();
+	for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling())
+	    if (child instanceof Element && ((Element) child).getTagName().equals(tagName))
+		nodes.add(child);
+	return new NodeList()
+	    {
+		public int getLength()
+		{ return nodes.size(); }
+
+		public Node item( int i )
+		{ return (Node) nodes.get( i ); }
+	    };
+    }
+
+    public static String allTextFromUniqueImmediateChild(Element elem, String childTagName)
+	throws DOMException
+    {
+	Element uniqueChild = uniqueImmediateChildByTagName( elem, childTagName );
+	if (uniqueChild == null)
+	    return null;
+	return DomParseUtils.allTextFromElement( uniqueChild );
+    }
+
+    public static Element uniqueImmediateChild(Element elem, String childTagName) 
+	throws DOMException
+    { return uniqueImmediateChildByTagName( elem, childTagName); }
+
+    /**
+     * @deprecated use uniqueImmediateChild(Element elem, String childTagName) 
+     */
+    public static Element uniqueImmediateChildByTagName(Element elem, String childTagName) 
+	throws DOMException
+    {
+	NodeList nl = getImmediateChildElementsByTagName(elem, childTagName);
+	int len = nl.getLength();
+	if (DEBUG)
+	    DebugUtils.myAssert( len <= 1 ,
+				 "There is more than one (" + len + ") child with tag name: " + 
+				 childTagName + "!!!" );
+	return (len == 1 ? (Element) nl.item( 0 ) : null);
+    }
+
+    /**
+     * @deprecated use Element.getAttribute(String val)
+     */
+    public static String attrValFromElement(Element element, String attrName)
+	throws DOMException
+    {
+	Attr attr = element.getAttributeNode( attrName );
+	return (attr == null ? null : attr.getValue());
+    }
+
+    private DomParseUtils()
+    {}
+}
+
+
diff --git a/src/classes/com/mchange/v2/async/AsynchronousRunner.java b/src/classes/com/mchange/v2/async/AsynchronousRunner.java
new file mode 100644
index 0000000..9cfe663
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/AsynchronousRunner.java
@@ -0,0 +1,56 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async;
+
+import com.mchange.v1.util.ClosableResource;
+
+public interface AsynchronousRunner extends ClosableResource
+{
+    public void postRunnable(Runnable r);
+
+
+    /**
+     * Finish with this AsynchronousRunner, and clean-up
+     * any Threads or resources it may hold.
+     *
+     * @param skip_remaining_tasks Should be regarded as
+     *        a hint, not a guarantee. If true, pending,
+     *        not-yet-performed tasks will be skipped,
+     *        if possible.
+     *        Currently executing tasks may or 
+     *        may not be interrupted. If false, all
+     *        previously scheduled tasks will be 
+     *        completed prior to clean-up. The method
+     *        returns immediately regardless.
+     */ 
+    public void close( boolean skip_remaining_tasks );
+
+    /**
+     * Clean-up resources held by this asynchronous runner
+     * as soon as possible. Remaining tasks are skipped if possible,
+     * and any tasks executing when close() is called may
+     * or may not be interrupted. Equivalent to close( true ).
+     */
+    public void close();
+}
diff --git a/src/classes/com/mchange/v2/async/CarefulRunnableQueue.java b/src/classes/com/mchange/v2/async/CarefulRunnableQueue.java
new file mode 100644
index 0000000..b77e532
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/CarefulRunnableQueue.java
@@ -0,0 +1,235 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.LinkedList;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import com.mchange.v2.util.ResourceClosedException;
+
+public class CarefulRunnableQueue implements RunnableQueue, Queuable, StrandedTaskReporting
+{
+    private final static MLogger logger = MLog.getLogger( CarefulRunnableQueue.class );
+
+    private List taskList = new LinkedList();
+    private TaskThread t  = new TaskThread();
+
+    private boolean shutdown_on_interrupt;
+
+    private boolean gentle_close_requested = false;
+
+    private List strandedTasks = null;
+
+    public CarefulRunnableQueue(boolean daemon, boolean shutdown_on_interrupt)
+    {
+	this.shutdown_on_interrupt = shutdown_on_interrupt;
+	t.setDaemon( daemon );
+	t.start();
+    }
+
+    public RunnableQueue asRunnableQueue()
+    { return this; }
+
+    public synchronized void postRunnable(Runnable r)
+    {
+	try
+	    {
+		if (gentle_close_requested)
+		    throw new ResourceClosedException("Attempted to post a task to a closing " +
+						      "CarefulRunnableQueue.");
+		
+		taskList.add(r);
+		this.notifyAll();
+	    }
+	catch (NullPointerException e)
+	    {
+		//e.printStackTrace();
+		if (Debug.DEBUG)
+		    {
+			if ( logger.isLoggable( MLevel.FINE ) )
+			    logger.log( MLevel.FINE, "NullPointerException while posting Runnable.", e );
+		    }
+		if (taskList == null)
+		    throw new ResourceClosedException("Attempted to post a task to a CarefulRunnableQueue " +
+						      "which has been closed, or whose TaskThread has been " +
+						      "interrupted.");
+		else 
+		    throw e;
+	    }
+    }
+
+    public synchronized void close( boolean skip_remaining_tasks )
+    {
+	if (skip_remaining_tasks)
+	    {
+		t.safeStop();
+		t.interrupt();
+	    }
+	else
+	    gentle_close_requested = true;
+    }
+
+    public synchronized void close()
+    { this.close( true ); }
+
+    public synchronized List getStrandedTasks()
+    {
+	try
+	    {
+		while (gentle_close_requested && taskList != null)
+		    this.wait();
+		return strandedTasks;
+	    }
+	catch (InterruptedException e) 
+	    {
+		// very, very rare I think...
+		// if necessary I'll try a more complex solution, but I don't think
+		// it's worth it.
+		//e.printStackTrace();
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, 
+				Thread.currentThread() + " interrupted while waiting for stranded tasks from CarefulRunnableQueue.",
+				e );
+
+		throw new RuntimeException(Thread.currentThread() + 
+					   " interrupted while waiting for stranded tasks from CarefulRunnableQueue.");
+	    }
+    }
+
+    private synchronized Runnable dequeueRunnable()
+    {
+	Runnable r = (Runnable) taskList.get(0);
+	taskList.remove(0);
+	return r;
+    }
+
+    private synchronized void awaitTask() throws InterruptedException
+    {
+	while (taskList.size() == 0) 
+	    {
+		if ( gentle_close_requested )
+		    {
+			t.safeStop(); // remember t == Thread.currentThread()
+			t.interrupt();
+		    }
+		this.wait();
+	    }
+    }
+
+    class TaskThread extends Thread
+    {
+	boolean should_stop = false;
+
+	TaskThread()
+	{ super("CarefulRunnableQueue.TaskThread"); }
+
+	public synchronized void safeStop()
+	{ should_stop = true; }
+
+	private synchronized boolean shouldStop()
+	{ return should_stop; }
+
+	public void run()
+	{
+	    try
+		{
+		    while (! shouldStop() )
+			{
+			    try
+				{
+				    awaitTask();
+				    Runnable r = dequeueRunnable();
+				    try
+					{ r.run(); }
+				    catch (Exception e)
+					{
+					    //System.err.println(this.getClass().getName() + " -- Unexpected exception in task!");
+					    //e.printStackTrace();
+
+					    if ( logger.isLoggable( MLevel.WARNING ) )
+						logger.log(MLevel.WARNING, this.getClass().getName() + " -- Unexpected exception in task!", e);
+					}
+				}
+			    catch (InterruptedException e)
+				{
+				    if (shutdown_on_interrupt)
+					{
+					    CarefulRunnableQueue.this.close( false );
+// 					    if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED ) 
+// 						System.err.println( this.toString() + 
+// 								    " interrupted. Shutting down after current tasks" +
+// 								    " have completed." );
+					    if ( logger.isLoggable( MLevel.INFO ) )
+						logger.info(this.toString() + 
+							    " interrupted. Shutting down after current tasks" +
+							    " have completed." );
+					}
+				    else
+					{
+// 					    if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED ) 
+// 						System.err.println( this.toString() + 
+// 								    " received interrupt. IGNORING." );
+					    logger.info(this.toString() + " received interrupt. IGNORING." );
+					}
+				}
+			}
+		}
+// 	    catch (ThreadDeath td) //DEBUG ONLY -- remove soon, swaldman 08-Jun-2003
+// 		{
+// 		    System.err.print("c3p0-TRAVIS: ");
+// 		    System.err.println(this.getName() + ": Some bastard used the deprecated stop() method to kill me!!!!");
+// 		    td.printStackTrace();
+// 		    throw td;
+// 		}
+// 	    catch (Throwable t) //DEBUG ONLY -- remove soon, swaldman 08-Jun-2003
+// 		{
+// 		    System.err.print("c3p0-TRAVIS: ");
+// 		    System.err.println(this.getName() + ": Some unexpected Throwable occurred and killed me!!!!");
+// 		    t.printStackTrace();
+// 		    if (t instanceof Error)
+// 			throw (Error) t;
+// 		    else if (t instanceof RuntimeException)
+// 			throw (RuntimeException) t;
+// 		    else
+// 			throw new InternalError( t.toString() ); //we don't expect any checked Exceptions can happen here.
+// 		}
+	    finally
+		{
+		    synchronized ( CarefulRunnableQueue.this )
+			{
+			    strandedTasks = Collections.unmodifiableList( taskList );
+			    taskList = null;
+			    t = null;
+			    CarefulRunnableQueue.this.notifyAll(); //if anyone is waiting for stranded tasks...
+			    //System.err.print("c3p0-TRAVIS: ");
+			    //System.err.println("TaskThread dead. strandedTasks: " + strandedTasks);
+			}
+		}
+	}
+    }
+}
+
diff --git a/src/classes/com/mchange/v2/async/Queuable.java b/src/classes/com/mchange/v2/async/Queuable.java
new file mode 100644
index 0000000..78b1350
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/Queuable.java
@@ -0,0 +1,29 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async;
+
+public interface Queuable extends AsynchronousRunner
+{
+    public RunnableQueue asRunnableQueue();
+}
diff --git a/src/classes/com/mchange/v2/async/RoundRobinAsynchronousRunner.java b/src/classes/com/mchange/v2/async/RoundRobinAsynchronousRunner.java
new file mode 100644
index 0000000..de0eeaa
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/RoundRobinAsynchronousRunner.java
@@ -0,0 +1,154 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async;
+
+import com.mchange.v2.log.*;
+import com.mchange.v2.util.ResourceClosedException;
+
+/**
+ * A class that provides for effecient asynchronous execution
+ * of multiple tasks that may block, but that do not contend
+ * for the same locks. The order in which tasks will be executed
+ * is not guaranteed.
+ */
+public class RoundRobinAsynchronousRunner implements AsynchronousRunner, Queuable
+{
+    private final static MLogger logger = MLog.getLogger( RoundRobinAsynchronousRunner.class );
+
+    //MT: unchanging, individual elements are thread-safe
+    final RunnableQueue[] rqs;
+
+    //MT: protected by this' lock
+    int task_turn = 0;
+
+    //MT: protected by this' lock
+    int view_turn = 0;
+
+    public RoundRobinAsynchronousRunner( int num_threads, boolean daemon )
+    {
+	this.rqs = new RunnableQueue[ num_threads ];
+	for(int i = 0; i < num_threads; ++i)
+	    rqs[i] = new CarefulRunnableQueue( daemon, false );
+    }
+
+    public synchronized void postRunnable(Runnable r)
+    { 
+	try
+	    {
+		int index = task_turn;
+		task_turn = (task_turn + 1) % rqs.length;
+		rqs[index].postRunnable( r );
+
+		/* we do this "long-hand" to avoid bad fragility if an exception */
+		/* occurs in postRunnable, causing the mod step of the original  */
+		/* concise code to get skipped, and leading (if                  */
+		/* task_turn == rqs.length - 1 when the exception occurs) to an  */
+		/* endless cascade of ArrayIndexOutOfBoundsExceptions.           */
+		/* we might alternatively have just put the mod step into a      */
+		/* finally block, but that's too fancy.                          */
+		/* thanks to Travis Reeder for reporting this problem.           */
+
+		//rqs[task_turn++].postRunnable( r );
+		//task_turn %= rqs.length;
+	    }
+	catch ( NullPointerException e )
+	    {
+		//e.printStackTrace();
+		if ( Debug.DEBUG )
+		    {
+			if ( logger.isLoggable( MLevel.FINE ) )
+			    logger.log( MLevel.FINE, "NullPointerException while posting Runnable -- Probably we're closed.", e );
+		    }
+		this.close( true );
+		throw new ResourceClosedException("Attempted to use a RoundRobinAsynchronousRunner in a closed or broken state.");
+	    }
+    }
+
+    public synchronized RunnableQueue asRunnableQueue()
+    { 
+	try
+	    {
+		int index = view_turn;
+		view_turn = (view_turn + 1) % rqs.length;
+		return new RunnableQueueView( index );
+		
+		/* same explanation as above */
+		
+		//RunnableQueue out = new RunnableQueueView( view_turn++ ); 
+		//view_turn %= rqs.length;
+		//return out;
+	    }
+	catch ( NullPointerException e )
+	    {
+		//e.printStackTrace();
+		if ( Debug.DEBUG )
+		    {
+			if ( logger.isLoggable( MLevel.FINE ) )
+			    logger.log( MLevel.FINE, "NullPointerException in asRunnableQueue() -- Probably we're closed.", e );
+		    }
+		this.close( true );
+		throw new ResourceClosedException("Attempted to use a RoundRobinAsynchronousRunner in a closed or broken state.");
+	    }
+    }
+
+    public synchronized void close( boolean skip_remaining_tasks )
+    {
+	for (int i = 0, len = rqs.length; i < len; ++i)
+	    {
+		attemptClose( rqs[i], skip_remaining_tasks );
+		rqs[i] = null;
+	    }
+    }
+
+    public void close()
+    { close( true ); }
+
+    static void attemptClose(RunnableQueue rq, boolean skip_remaining_tasks)
+    {
+	try { rq.close( skip_remaining_tasks ); }
+	catch ( Exception e ) 
+	    { 
+		//e.printStackTrace(); 
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "RunnableQueue close FAILED.", e );
+	    }
+    }
+
+    class RunnableQueueView implements RunnableQueue
+    {
+	final int rq_num;
+
+	RunnableQueueView( int rq_num )
+	{ this.rq_num = rq_num; }
+
+	public void postRunnable(Runnable r)
+	{ rqs[ rq_num ].postRunnable( r ); }
+	
+	public void close( boolean skip_remaining_tasks )
+	{ }
+	
+	public void close()
+	{ /* ignore */ }
+    }
+}
diff --git a/src/classes/com/mchange/v2/async/RunnableQueue.java b/src/classes/com/mchange/v2/async/RunnableQueue.java
new file mode 100644
index 0000000..3bf5578
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/RunnableQueue.java
@@ -0,0 +1,32 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async;
+
+/**
+ *  RunnableQueues guarantee that tasks will be
+ *  executed in order, where other AsynchronousRunners
+ *  may not.
+ */
+public interface RunnableQueue extends AsynchronousRunner
+{}
diff --git a/src/classes/com/mchange/v2/async/StrandedTaskReporting.java b/src/classes/com/mchange/v2/async/StrandedTaskReporting.java
new file mode 100644
index 0000000..bfbf91e
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/StrandedTaskReporting.java
@@ -0,0 +1,42 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async;
+
+import java.util.List;
+
+public interface StrandedTaskReporting
+{
+    /**
+     * makes available any tasks that were unperformed when
+     * this AsynchronousRunner was closed, either explicitly
+     * using close() or close( true ), or implicitly because
+     * some failure or corruption killed the Object (most likely
+     * a Thread interruption.
+     *
+     * @return null if the AsynchronousRunner is still alive, a List
+     *  of Runnables otherwise, which will be empty only if all tasks
+     *  were performed before the AsynchronousRunner shut down.
+     */
+    public List getStrandedTasks();
+}
diff --git a/src/classes/com/mchange/v2/async/ThreadPerTaskAsynchronousRunner.java b/src/classes/com/mchange/v2/async/ThreadPerTaskAsynchronousRunner.java
new file mode 100644
index 0000000..8a26a90
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/ThreadPerTaskAsynchronousRunner.java
@@ -0,0 +1,261 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async;
+
+import java.util.*;
+import com.mchange.v2.log.*;
+import com.mchange.v2.util.ResourceClosedException;
+
+public class ThreadPerTaskAsynchronousRunner implements AsynchronousRunner
+{
+    final static int PRESUME_DEADLOCKED_MULTIPLE = 3; //after three times the interrupt period, we presume deadlock
+
+    final static MLogger logger = MLog.getLogger( ThreadPerTaskAsynchronousRunner.class );
+
+    //MT: unchanged post-ctor
+    final int  max_task_threads;
+    final long interrupt_task_delay;
+    
+    //MT: protected by this' lock
+    LinkedList queue =  new LinkedList(); 
+    ArrayList  running = new ArrayList(); //as a Collection -- duplicate-accepting-ness is important, order is not
+    ArrayList  deadlockSnapshot = null;
+    boolean still_open = true;
+
+    //MT: thread-safe and not reassigned post-ctor
+    Thread dispatchThread = new DispatchThread();
+    Timer interruptAndDeadlockTimer;
+
+    public ThreadPerTaskAsynchronousRunner(int max_task_threads)
+    { this( max_task_threads, 0); }
+
+    public ThreadPerTaskAsynchronousRunner(int max_task_threads, long interrupt_task_delay)
+    {
+	this.max_task_threads = max_task_threads;
+	this.interrupt_task_delay = interrupt_task_delay;
+	if ( hasIdTimer() )
+	    {
+		interruptAndDeadlockTimer = new Timer( true );
+		TimerTask deadlockChecker = new TimerTask()
+		    {
+			public void run()
+			{ checkForDeadlock(); }
+		    };
+		long delay = interrupt_task_delay * PRESUME_DEADLOCKED_MULTIPLE;
+		interruptAndDeadlockTimer.schedule(deadlockChecker, delay, delay);
+	    }
+
+	dispatchThread.start();
+    }
+
+    private boolean hasIdTimer()
+    { return (interrupt_task_delay > 0); }
+
+    public synchronized void postRunnable(Runnable r)
+    {
+	if ( still_open )
+	    {
+		queue.add( r );
+		this.notifyAll();
+	    }
+	else
+	    throw new ResourceClosedException("Attempted to use a ThreadPerTaskAsynchronousRunner in a closed or broken state.");
+
+    }
+
+    public void close()
+    { close( true ); }
+
+    public synchronized void close( boolean skip_remaining_tasks )
+    {
+	if ( still_open )
+	    {
+		this.still_open = false;
+		if (skip_remaining_tasks)
+		    {
+			queue.clear();
+			for (Iterator ii = running.iterator(); ii.hasNext(); )
+			    ((Thread) ii.next()).interrupt();
+			closeThreadResources();
+		    }
+	    }
+    }
+
+    public synchronized int getRunningCount()
+    { return running.size(); }
+
+    public synchronized Collection getRunningTasks()
+    { return (Collection) running.clone(); }
+
+    public synchronized int getWaitingCount()
+    { return queue.size(); }
+
+    public synchronized Collection getWaitingTasks()
+    { return (Collection) queue.clone(); }
+
+    public synchronized boolean isClosed()
+    { return !still_open; }
+
+    public synchronized boolean isDoneAndGone()
+    { return (!dispatchThread.isAlive() && running.isEmpty() && interruptAndDeadlockTimer == null); }
+
+    private synchronized void acknowledgeComplete(TaskThread tt)
+    {
+	if (! tt.isCompleted())
+	    {
+		running.remove( tt );
+		tt.markCompleted();
+		ThreadPerTaskAsynchronousRunner.this.notifyAll();
+		
+		if (! still_open && queue.isEmpty() && running.isEmpty())
+		    closeThreadResources();
+	    }
+    }
+
+    private synchronized void checkForDeadlock()
+    {
+	if (deadlockSnapshot == null)
+	    {
+		if (running.size() == max_task_threads)
+		    deadlockSnapshot = (ArrayList) running.clone();
+	    }
+	else if (running.size() < max_task_threads)
+	    deadlockSnapshot = null;
+	else if (deadlockSnapshot.equals( running )) //deadlock!
+	    {
+		if (logger.isLoggable(MLevel.WARNING))
+		    {
+			StringBuffer warningMsg = new StringBuffer(1024);
+			warningMsg.append("APPARENT DEADLOCK! (");
+			warningMsg.append( this );
+			warningMsg.append(") Deadlocked threads (unresponsive to interrupt()) are being set aside as hopeless and up to ");
+			warningMsg.append( max_task_threads );
+			warningMsg.append(" may now be spawned for new tasks. If tasks continue to deadlock, you may run out of memory. Deadlocked task list: ");
+			for (int i = 0, len = deadlockSnapshot.size(); i < len; ++i)
+			    {
+				if (i != 0) warningMsg.append(", ");
+				warningMsg.append( ((TaskThread) deadlockSnapshot.get(i)).getTask() );
+			    }
+			
+			logger.log(MLevel.WARNING, warningMsg.toString());
+		    }
+
+		// note "complete" here means from the perspective of the async runner, as complete
+		// as it will ever be, since the task is presumed hopelessly hung
+		for (int i = 0, len = deadlockSnapshot.size(); i < len; ++i)
+		    acknowledgeComplete( (TaskThread) deadlockSnapshot.get(i) ); 
+		deadlockSnapshot = null;
+	    }
+	else
+	    deadlockSnapshot = (ArrayList) running.clone();
+    }
+
+    private void closeThreadResources()
+    {
+	if (interruptAndDeadlockTimer != null)
+	    {
+		interruptAndDeadlockTimer.cancel();
+		interruptAndDeadlockTimer = null;
+	    }
+	dispatchThread.interrupt();
+    }
+
+    class DispatchThread extends Thread
+    {
+	DispatchThread()
+	{ super( "Dispatch-Thread-for-" + ThreadPerTaskAsynchronousRunner.this ); }
+
+	public void run()
+	{
+	    synchronized (ThreadPerTaskAsynchronousRunner.this)
+		{
+		    try
+			{
+			    while (true)
+				{
+				    while (queue.isEmpty() || running.size() == max_task_threads)
+					ThreadPerTaskAsynchronousRunner.this.wait();
+				    
+				    Runnable next = (Runnable) queue.remove(0);
+				    TaskThread doer = new TaskThread( next );
+				    doer.start();
+				    running.add( doer );
+				}
+			}
+		    catch (InterruptedException e)
+			{
+			    if (still_open) //we're not closed...
+				{
+				    if ( logger.isLoggable( MLevel.WARNING ) )
+					logger.log( MLevel.WARNING, this.getName() + " unexpectedly interrupted! Shutting down!" );
+				    close( false );
+				}
+			}
+		}
+	}
+    }
+
+    class TaskThread extends Thread
+    {
+	//MT: post-ctor constant
+	Runnable r;
+
+	//MT: protected by this' lock
+	boolean completed = false;
+
+	TaskThread( Runnable r )
+	{ 
+	    super( "Task-Thread-for-" + ThreadPerTaskAsynchronousRunner.this ); 
+	    this.r = r;
+	}
+
+	Runnable getTask()
+	{ return r; }
+
+	synchronized void markCompleted()
+	{ completed = true; }
+
+	synchronized boolean isCompleted()
+	{ return completed; }
+
+	public void run()
+	{
+	    try
+		{
+		    if (hasIdTimer())
+			{
+			    TimerTask interruptTask = new TimerTask()
+				{
+				    public void run()
+				    { TaskThread.this.interrupt(); }
+				};
+			    interruptAndDeadlockTimer.schedule( interruptTask , interrupt_task_delay );
+			}
+		    r.run();
+		}
+	    finally
+		{ acknowledgeComplete( this ); }
+	}
+    }
+}
diff --git a/src/classes/com/mchange/v2/async/ThreadPoolAsynchronousRunner.java b/src/classes/com/mchange/v2/async/ThreadPoolAsynchronousRunner.java
new file mode 100644
index 0000000..230a036
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/ThreadPoolAsynchronousRunner.java
@@ -0,0 +1,718 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async;
+
+import java.util.*;
+import com.mchange.v2.log.*;
+
+import java.io.StringWriter;
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import com.mchange.v2.io.IndentedWriter;
+import com.mchange.v2.util.ResourceClosedException;
+
+public final class ThreadPoolAsynchronousRunner implements AsynchronousRunner
+{
+    final static MLogger logger = MLog.getLogger( ThreadPoolAsynchronousRunner.class );
+
+    final static int POLL_FOR_STOP_INTERVAL                       = 5000; //milliseconds
+
+    final static int DFLT_DEADLOCK_DETECTOR_INTERVAL              = 10000; //milliseconds
+    final static int DFLT_INTERRUPT_DELAY_AFTER_APPARENT_DEADLOCK = 60000; //milliseconds
+    final static int DFLT_MAX_INDIVIDUAL_TASK_TIME                = 0;     //milliseconds, <= 0 means don't enforce a max task time
+
+    final static int DFLT_MAX_EMERGENCY_THREADS                   = 10;
+
+    int deadlock_detector_interval;
+    int interrupt_delay_after_apparent_deadlock;
+    int max_individual_task_time;
+
+    int        num_threads;
+    boolean    daemon;
+    HashSet    managed;
+    HashSet    available;
+    LinkedList pendingTasks;
+
+    Timer myTimer;
+    boolean should_cancel_timer;
+
+    TimerTask deadlockDetector = new DeadlockDetector();
+    TimerTask replacedThreadInterruptor = null;
+
+    Map stoppedThreadsToStopDates = new HashMap();
+
+    private ThreadPoolAsynchronousRunner( int num_threads, 
+                    boolean daemon, 
+                    int max_individual_task_time,
+                    int deadlock_detector_interval, 
+                    int interrupt_delay_after_apparent_deadlock,
+                    Timer myTimer,
+                    boolean should_cancel_timer )
+    {
+        this.num_threads = num_threads;
+        this.daemon = daemon;
+        this.max_individual_task_time = max_individual_task_time;
+        this.deadlock_detector_interval = deadlock_detector_interval;
+        this.interrupt_delay_after_apparent_deadlock = interrupt_delay_after_apparent_deadlock;
+        this.myTimer = myTimer;
+        this.should_cancel_timer = should_cancel_timer;
+
+        recreateThreadsAndTasks();
+
+        myTimer.schedule( deadlockDetector, deadlock_detector_interval, deadlock_detector_interval );
+
+    }
+
+
+    public ThreadPoolAsynchronousRunner( int num_threads, 
+                    boolean daemon, 
+                    int max_individual_task_time,
+                    int deadlock_detector_interval, 
+                    int interrupt_delay_after_apparent_deadlock,
+                    Timer myTimer )
+    {
+        this( num_threads, 
+                        daemon, 
+                        max_individual_task_time,
+                        deadlock_detector_interval, 
+                        interrupt_delay_after_apparent_deadlock,
+                        myTimer, 
+                        false );
+    }
+
+    public ThreadPoolAsynchronousRunner( int num_threads, 
+                    boolean daemon, 
+                    int max_individual_task_time,
+                    int deadlock_detector_interval, 
+                    int interrupt_delay_after_apparent_deadlock )
+    {
+        this( num_threads, 
+                        daemon, 
+                        max_individual_task_time,
+                        deadlock_detector_interval, 
+                        interrupt_delay_after_apparent_deadlock,
+                        new Timer( true ), 
+                        true );
+    }
+
+    public ThreadPoolAsynchronousRunner( int num_threads, boolean daemon, Timer sharedTimer )
+    { 
+        this( num_threads, 
+                        daemon, 
+                        DFLT_MAX_INDIVIDUAL_TASK_TIME, 
+                        DFLT_DEADLOCK_DETECTOR_INTERVAL, 
+                        DFLT_INTERRUPT_DELAY_AFTER_APPARENT_DEADLOCK, 
+                        sharedTimer, 
+                        false ); 
+    }
+
+    public ThreadPoolAsynchronousRunner( int num_threads, boolean daemon )
+    { 
+        this( num_threads, 
+                        daemon, 
+                        DFLT_MAX_INDIVIDUAL_TASK_TIME, 
+                        DFLT_DEADLOCK_DETECTOR_INTERVAL, 
+                        DFLT_INTERRUPT_DELAY_AFTER_APPARENT_DEADLOCK, 
+                        new Timer( true ), 
+                        true ); }
+
+    public synchronized void postRunnable(Runnable r)
+    {
+        try
+        {
+            pendingTasks.add( r );
+            this.notifyAll();
+        }
+        catch ( NullPointerException e )
+        {
+            //e.printStackTrace();
+            if ( Debug.DEBUG )
+            {
+                if ( logger.isLoggable( MLevel.FINE ) )
+                    logger.log( MLevel.FINE, "NullPointerException while posting Runnable -- Probably we're closed.", e );
+            }
+            throw new ResourceClosedException("Attempted to use a ThreadPoolAsynchronousRunner in a closed or broken state.");
+        }
+    }
+
+    public synchronized int getThreadCount()
+    { return managed.size(); }
+
+    public void close( boolean skip_remaining_tasks )
+    {
+        synchronized ( this )
+        {
+            if (managed == null) return;
+            deadlockDetector.cancel();
+            //replacedThreadInterruptor.cancel();
+            if (should_cancel_timer)
+                myTimer.cancel();
+            myTimer = null;
+            for (Iterator ii = managed.iterator(); ii.hasNext(); )
+            { 
+                PoolThread stopMe = (PoolThread) ii.next();
+                stopMe.gentleStop();
+                if (skip_remaining_tasks)
+                    stopMe.interrupt();
+            }
+            managed = null;
+
+            if (!skip_remaining_tasks)
+            {
+                for (Iterator ii = pendingTasks.iterator(); ii.hasNext(); )
+                {
+                    Runnable r = (Runnable) ii.next();
+                    new Thread(r).start();
+                    ii.remove();
+                }
+            }
+            available = null;
+            pendingTasks = null;
+        }
+    }
+
+    public void close()
+    { close( true ); }
+
+    public synchronized int getActiveCount()
+    { return managed.size() - available.size(); }
+
+    public synchronized int getIdleCount()
+    { return available.size(); }
+
+    public synchronized int getPendingTaskCount()
+    { return pendingTasks.size(); }
+
+    public synchronized String getStatus()
+    { 
+        /*
+	  StringBuffer sb = new StringBuffer( 512 );
+	  sb.append( this.toString() );
+	  sb.append( ' ' );
+	  appendStatusString( sb );
+	  return sb.toString();
+         */
+
+        return getMultiLineStatusString();
+    }
+
+    // done reflectively for jdk 1.3/1.4 compatability
+    public synchronized String getStackTraces()
+    { return getStackTraces(0); }
+
+    // protected by ThreadPoolAsynchronousRunner.this' lock
+    // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock
+    private String getStackTraces(int initial_indent)
+    {
+        if (managed == null)
+            return null;
+
+        try
+        {
+            Method m = Thread.class.getMethod("getStackTrace", null);
+
+            StringWriter sw = new StringWriter(2048);
+            IndentedWriter iw = new IndentedWriter( sw );
+            for (int i = 0; i < initial_indent; ++i)
+                iw.upIndent();
+            for (Iterator ii = managed.iterator(); ii.hasNext(); )
+            {
+                Object poolThread = ii.next();
+                Object[] stackTraces = (Object[]) m.invoke( poolThread, null );
+                iw.println( poolThread );
+                iw.upIndent();
+                for (int i = 0, len = stackTraces.length; i < len; ++i)
+                    iw.println( stackTraces[i] );
+                iw.downIndent();
+            }
+            for (int i = 0; i < initial_indent; ++i)
+                iw.downIndent();
+            iw.flush(); // useless, but I feel better
+            String out = sw.toString();
+            iw.close(); // useless, but I feel better;
+            return out;
+        }
+        catch (NoSuchMethodException e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.fine( this + ": strack traces unavailable because this is a pre-Java 1.5 VM.");
+            return null;
+        }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, this + ": An Exception occurred while trying to extract PoolThread stack traces.", e);
+            return null;
+        }
+    }
+
+    public synchronized String getMultiLineStatusString()
+    { return this.getMultiLineStatusString(0); }
+
+    // protected by ThreadPoolAsynchronousRunner.this' lock
+    // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock
+    private String getMultiLineStatusString(int initial_indent)
+    {
+        try
+        {
+            StringWriter sw = new StringWriter(2048);
+            IndentedWriter iw = new IndentedWriter( sw );
+
+            for (int i = 0; i < initial_indent; ++i)
+                iw.upIndent();
+
+            if (managed == null)
+            {
+                iw.print("[");
+                iw.print( this );
+                iw.println(" closed.]");
+            }
+            else
+            {
+                HashSet active = (HashSet) managed.clone();
+                active.removeAll( available );
+
+                iw.print("Managed Threads: ");
+                iw.println( managed.size() );
+                iw.print("Active Threads: ");
+                iw.println( active.size() );
+                iw.println("Active Tasks: ");
+                iw.upIndent();
+                for (Iterator ii = active.iterator(); ii.hasNext(); )
+                {
+                    PoolThread pt = (PoolThread) ii.next();
+                    iw.print( pt.getCurrentTask() );
+                    iw.print( " (");
+                    iw.print( pt.getName() );
+                    iw.println(')');
+                }
+                iw.downIndent();
+                iw.println("Pending Tasks: ");
+                iw.upIndent();
+                for (int i = 0, len = pendingTasks.size(); i < len; ++i)
+                    iw.println( pendingTasks.get( i ) );
+                iw.downIndent();
+            }
+
+            for (int i = 0; i < initial_indent; ++i)
+                iw.downIndent();
+            iw.flush(); // useless, but I feel better
+            String out = sw.toString();
+            iw.close(); // useless, but I feel better;
+            return out;
+        }
+        catch (IOException e)
+        {
+            if (logger.isLoggable( MLevel.WARNING ))
+                logger.log( MLevel.WARNING, "Huh? An IOException when working with a StringWriter?!?", e);
+            throw new RuntimeException("Huh? An IOException when working with a StringWriter?!? " + e);
+        }
+    }
+
+    // protected by ThreadPoolAsynchronousRunner.this' lock
+    // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock
+    private void appendStatusString( StringBuffer sb )
+    {
+        if (managed == null)
+            sb.append( "[closed]" );
+        else
+        {
+            HashSet active = (HashSet) managed.clone();
+            active.removeAll( available );
+            sb.append("[num_managed_threads: ");
+            sb.append( managed.size() );
+            sb.append(", num_active: ");
+            sb.append( active.size() );
+            sb.append("; activeTasks: ");
+            boolean first = true;
+            for (Iterator ii = active.iterator(); ii.hasNext(); )
+            {
+                if (first)
+                    first = false;
+                else
+                    sb.append(", ");
+                PoolThread pt = (PoolThread) ii.next();
+                sb.append( pt.getCurrentTask() );
+                sb.append( " (");
+                sb.append( pt.getName() );
+                sb.append(')');
+            }
+            sb.append("; pendingTasks: ");
+            for (int i = 0, len = pendingTasks.size(); i < len; ++i)
+            {
+                if (i != 0) sb.append(", ");
+                sb.append( pendingTasks.get( i ) );
+            }
+            sb.append(']');
+        }
+    }
+
+    // protected by ThreadPoolAsynchronousRunner.this' lock
+    // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock (or is ctor)
+    private void recreateThreadsAndTasks()
+    {
+        if ( this.managed != null)
+        {
+            Date aboutNow = new Date();
+            for (Iterator ii = managed.iterator(); ii.hasNext(); )
+            {
+                PoolThread pt = (PoolThread) ii.next();
+                pt.gentleStop();
+                stoppedThreadsToStopDates.put( pt, aboutNow );
+                ensureReplacedThreadsProcessing();
+            }
+        }
+
+        this.managed = new HashSet();
+        this.available = new HashSet();
+        this.pendingTasks = new LinkedList();
+        for (int i = 0; i < num_threads; ++i)
+        {
+            Thread t = new PoolThread(i, daemon);
+            managed.add( t );
+            available.add( t );
+            t.start();
+        }
+    }
+
+    // protected by ThreadPoolAsynchronousRunner.this' lock
+    // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock
+    private void processReplacedThreads()
+    {
+        long about_now = System.currentTimeMillis();
+        for (Iterator ii = stoppedThreadsToStopDates.keySet().iterator(); ii.hasNext(); )
+        {
+            PoolThread pt = (PoolThread) ii.next();
+            if (! pt.isAlive())
+                ii.remove();
+            else
+            {
+                Date d = (Date) stoppedThreadsToStopDates.get( pt );
+                if ((about_now - d.getTime()) > interrupt_delay_after_apparent_deadlock)
+                {
+                    if (logger.isLoggable(MLevel.WARNING))
+                        logger.log(MLevel.WARNING, 
+                                        "Task " + pt.getCurrentTask() + " (in deadlocked PoolThread) failed to complete in maximum time " +
+                                        interrupt_delay_after_apparent_deadlock + "ms. Trying interrupt().");
+                    pt.interrupt();
+                    ii.remove();
+                }
+                //else keep waiting...
+            }
+            if (stoppedThreadsToStopDates.isEmpty())
+                stopReplacedThreadsProcessing();
+        }
+    }
+
+    // protected by ThreadPoolAsynchronousRunner.this' lock
+    // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock
+    private void ensureReplacedThreadsProcessing()
+    {
+        if (replacedThreadInterruptor == null)
+        {
+            if (logger.isLoggable( MLevel.FINE ))
+                logger.fine("Apparently some threads have been replaced. Replacement thread processing enabled.");
+
+            this.replacedThreadInterruptor = new ReplacedThreadInterruptor();
+            int replacedThreadProcessDelay = interrupt_delay_after_apparent_deadlock / 4;
+            myTimer.schedule( replacedThreadInterruptor, replacedThreadProcessDelay, replacedThreadProcessDelay );
+        }
+    }
+
+    // protected by ThreadPoolAsynchronousRunner.this' lock
+    // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock
+    private void stopReplacedThreadsProcessing()
+    {
+        if (this.replacedThreadInterruptor != null)
+        {
+            this.replacedThreadInterruptor.cancel();
+            this.replacedThreadInterruptor = null;
+
+            if (logger.isLoggable( MLevel.FINE ))
+                logger.fine("Apparently all replaced threads have either completed their tasks or been interrupted(). " +
+                "Replacement thread processing cancelled.");
+        }
+    }
+
+    // protected by ThreadPoolAsynchronousRunner.this' lock
+    // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock
+    private void shuttingDown( PoolThread pt )
+    {
+        if (managed != null && managed.contains( pt )) //we are not closed, and this was a thread in the current pool, not a replaced thread
+        {
+            managed.remove( pt );
+            available.remove( pt );
+            PoolThread replacement = new PoolThread( pt.getIndex(), daemon );
+            managed.add( replacement );
+            available.add( replacement );
+            replacement.start();
+        }
+    }
+
+
+    class PoolThread extends Thread
+    {
+        // protected by ThreadPoolAsynchronousRunner.this' lock
+        Runnable currentTask;
+
+        // protected by ThreadPoolAsynchronousRunner.this' lock
+        boolean should_stop;
+
+        // post ctor immutable
+        int index;
+
+        // not shared. only accessed by the PoolThread itself
+        TimerTask maxIndividualTaskTimeEnforcer = null;
+
+        PoolThread(int index, boolean daemon)
+        {
+            this.setName( this.getClass().getName() + "-#" + index);
+            this.setDaemon( daemon );
+            this.index = index;
+        }
+
+        public int getIndex()
+        { return index; }
+
+        // protected by ThreadPoolAsynchronousRunner.this' lock
+        // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock
+        void gentleStop()
+        { should_stop = true; }
+
+        // protected by ThreadPoolAsynchronousRunner.this' lock
+        // BE SURE CALLER OWNS ThreadPoolAsynchronousRunner.this' lock
+        Runnable getCurrentTask()
+        { return currentTask; }
+
+        // no need to sync. data not shared
+        private /* synchronized */  void setMaxIndividualTaskTimeEnforcer()
+        {
+            this.maxIndividualTaskTimeEnforcer = new MaxIndividualTaskTimeEnforcer( this );
+            myTimer.schedule( maxIndividualTaskTimeEnforcer, max_individual_task_time );
+        }
+
+        // no need to sync. data not shared
+        private /* synchronized */ void cancelMaxIndividualTaskTimeEnforcer()
+        {
+            this.maxIndividualTaskTimeEnforcer.cancel();
+            this.maxIndividualTaskTimeEnforcer = null;
+        }
+
+        public void run()
+        {
+            try
+            {
+                thread_loop:
+                    while (true)
+                    {
+                        Runnable myTask;
+                        synchronized ( ThreadPoolAsynchronousRunner.this )
+                        {
+                            while ( !should_stop && pendingTasks.size() == 0 )
+                                ThreadPoolAsynchronousRunner.this.wait( POLL_FOR_STOP_INTERVAL );
+                            if (should_stop) 
+                                break thread_loop;
+
+                            if (! available.remove( this ) )
+                                throw new InternalError("An unavailable PoolThread tried to check itself out!!!");
+                            myTask = (Runnable) pendingTasks.remove(0);
+                            currentTask = myTask;
+                        }
+                        try
+                        { 
+                            if (max_individual_task_time > 0)
+                                setMaxIndividualTaskTimeEnforcer();
+                            myTask.run(); 
+                        }
+                        catch ( RuntimeException e )
+                        {
+                            if ( logger.isLoggable( MLevel.WARNING ) )
+                                logger.log(MLevel.WARNING, this + " -- caught unexpected Exception while executing posted task.", e);
+                            //e.printStackTrace();
+                        }
+                        finally
+                        {
+                            if ( maxIndividualTaskTimeEnforcer != null )
+                                cancelMaxIndividualTaskTimeEnforcer();
+
+                            synchronized ( ThreadPoolAsynchronousRunner.this )
+                            {
+                                if (should_stop)
+                                    break thread_loop;
+
+                                if ( available != null && ! available.add( this ) )
+                                    throw new InternalError("An apparently available PoolThread tried to check itself in!!!");
+                                currentTask = null;
+                            }
+                        }
+                    }
+            }
+            catch ( InterruptedException exc )
+            {
+//              if ( Debug.TRACE > Debug.TRACE_NONE )
+//              System.err.println(this + " interrupted. Shutting down.");
+
+                if ( Debug.TRACE > Debug.TRACE_NONE && logger.isLoggable( MLevel.FINE ) )
+                    logger.fine(this + " interrupted. Shutting down.");
+            }
+
+            synchronized ( ThreadPoolAsynchronousRunner.this )
+            { ThreadPoolAsynchronousRunner.this.shuttingDown( this ); }
+        }
+    }
+
+    class DeadlockDetector extends TimerTask
+    {
+        LinkedList last = null;
+        LinkedList current = null;
+
+        public void run()
+        {
+            boolean run_stray_tasks = false;
+            synchronized ( ThreadPoolAsynchronousRunner.this )
+            { 
+                if (pendingTasks.size() == 0)
+                {
+                    last = null;
+                    return;
+                }
+
+                current = (LinkedList) pendingTasks.clone();
+                if ( current.equals( last ) )
+                {
+                    //System.err.println(this + " -- APPARENT DEADLOCK!!! Creating emergency threads for unassigned pending tasks!");
+                    if ( logger.isLoggable( MLevel.WARNING ) )
+                    {
+                        logger.warning(this + " -- APPARENT DEADLOCK!!! Creating emergency threads for unassigned pending tasks!");
+                        StringWriter sw = new StringWriter( 4096 );
+                        PrintWriter pw = new PrintWriter( sw );
+                        //StringBuffer sb = new StringBuffer( 512 );
+                        //appendStatusString( sb );
+                        //System.err.println( sb.toString() );
+                        pw.print( this );
+                        pw.println( " -- APPARENT DEADLOCK!!! Complete Status: ");
+                        pw.print( ThreadPoolAsynchronousRunner.this.getMultiLineStatusString( 1 ) );
+                        pw.println("Pool thread stack traces:"); 
+                        String stackTraces = getStackTraces( 1 );
+                        if (stackTraces == null)
+                            pw.println("\t[Stack traces of deadlocked task threads not available.]");
+                        else
+                            pw.println( stackTraces );
+                        pw.flush(); //superfluous, but I feel better
+                        logger.warning( sw.toString() );
+                        pw.close(); //superfluous, but I feel better
+                    }
+                    recreateThreadsAndTasks();
+                    run_stray_tasks = true;
+                }
+            }
+            if (run_stray_tasks)
+            {
+                AsynchronousRunner ar = new ThreadPerTaskAsynchronousRunner( DFLT_MAX_EMERGENCY_THREADS, max_individual_task_time );
+                for ( Iterator ii = current.iterator(); ii.hasNext(); )
+                    ar.postRunnable( (Runnable) ii.next() );
+                ar.close( false ); //tell the emergency runner to close itself when its tasks are complete
+                last = null;
+            }
+            else
+                last = current;
+
+            // under some circumstances, these lists seem to hold onto a lot of memory... presumably this
+            // is when long pending task lists build up for some reason... nevertheless, let's dereference
+            // things as soon as possible. [Thanks to Venkatesh Seetharamaiah for calling attention to this
+            // issue, and for documenting the source of object retention.]
+            current = null;
+        }
+    }
+
+    class MaxIndividualTaskTimeEnforcer extends TimerTask
+    {
+        PoolThread pt;
+        Thread     interruptMe;
+        String     threadStr;
+        String     fixedTaskStr;
+
+        MaxIndividualTaskTimeEnforcer(PoolThread pt)
+        { 
+            this.pt = pt;
+            this.interruptMe = pt;
+            this.threadStr = pt.toString();
+            this.fixedTaskStr = null;
+        }
+
+        MaxIndividualTaskTimeEnforcer(Thread interruptMe, String threadStr, String fixedTaskStr)
+        { 
+            this.pt = null; 
+            this.interruptMe = interruptMe;
+            this.threadStr = threadStr;
+            this.fixedTaskStr = fixedTaskStr;
+        }
+
+        public void run() 
+        { 
+            String taskStr;
+
+            if (fixedTaskStr != null)
+                taskStr = fixedTaskStr;
+            else if (pt != null)
+            {
+                synchronized (ThreadPoolAsynchronousRunner.this)
+                { taskStr = String.valueOf( pt.getCurrentTask() ); }
+            }
+            else
+                taskStr = "Unknown task?!";
+
+            if (logger.isLoggable( MLevel.WARNING ))
+                logger.warning("A task has exceeded the maximum allowable task time. Will interrupt() thread [" + threadStr
+                                + "], with current task: " + taskStr);
+
+            interruptMe.interrupt(); 
+
+            if (logger.isLoggable( MLevel.WARNING ))
+                logger.warning("Thread [" + threadStr + "] interrupted.");
+        } 
+    }
+
+    //not currently used...
+    private void runInEmergencyThread( final Runnable r )
+    {
+        final Thread t = new Thread( r );
+        t.start();
+        if (max_individual_task_time > 0)
+        {
+            TimerTask maxIndividualTaskTimeEnforcer = new MaxIndividualTaskTimeEnforcer(t, t + " [One-off emergency thread!!!]", r.toString());
+            myTimer.schedule( maxIndividualTaskTimeEnforcer, max_individual_task_time );
+        }
+    }
+
+    class ReplacedThreadInterruptor extends TimerTask
+    {
+        public void run()
+        {
+            synchronized (ThreadPoolAsynchronousRunner.this)
+            { processReplacedThreads(); }
+        }
+    }
+}
diff --git a/src/classes/com/mchange/v2/async/junit/ThreadPerTaskAsynchronousRunnerJUnitTestCase.java b/src/classes/com/mchange/v2/async/junit/ThreadPerTaskAsynchronousRunnerJUnitTestCase.java
new file mode 100644
index 0000000..52f8ca1
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/junit/ThreadPerTaskAsynchronousRunnerJUnitTestCase.java
@@ -0,0 +1,208 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async.junit;
+
+import junit.framework.*;
+import com.mchange.v2.async.*;
+
+public class ThreadPerTaskAsynchronousRunnerJUnitTestCase extends TestCase
+{
+    ThreadPerTaskAsynchronousRunner runner;
+
+    boolean no_go = true;
+    int gone = 0;
+
+    protected void setUp() 
+    {
+	runner = new ThreadPerTaskAsynchronousRunner(5);
+    }
+
+    protected void tearDown() 
+    { 
+	runner.close(); 
+	go(); //get any interrupt ignorers going...
+    }
+
+    private synchronized void go()
+    {
+	no_go = false;
+	this.notifyAll();
+    }
+
+    public void testBasicBehavior()
+    {
+	try
+	    {
+		DumbTask dt = new DumbTask();
+		for( int i = 0; i < 10; ++i )
+		    runner.postRunnable( dt );
+		Thread.sleep(1000); // not strictly safe, but should be plenty of time to get our tasks to the wait loop...
+		assertEquals( "running count should be 5", 5, runner.getRunningCount() );
+		assertEquals( "waiting count should be 5", 5, runner.getWaitingCount() );
+		go();
+		Thread.sleep(1000); // not strictly safe, but should be plenty of time to get our tasks to finish...
+		assertEquals( "running should be done.", 0, runner.getRunningCount() );
+		assertEquals( "waiting should be done.", 0, runner.getWaitingCount() );
+	    }
+	catch (InterruptedException e)
+	    {
+		e.printStackTrace();
+		fail("Unexpected InterruptedException: " + e);
+	    }
+    }
+
+    public void testBasicBehaviorFastNoSkipClose()
+    {
+	try
+	    {
+		DumbTask dt = new DumbTask();
+		for( int i = 0; i < 10; ++i )
+		    runner.postRunnable( dt );
+		runner.close( false );
+		Thread.sleep(1000); // not strictly safe, but should be plenty of time to get our tasks to the wait loop...
+		assertEquals( "running count should be 5", 5, runner.getRunningCount() );
+		assertEquals( "waiting count should be 5", 5, runner.getWaitingCount() );
+		go();
+		Thread.sleep(1000); // not strictly safe, but should be plenty of time to get our tasks to finish...
+		assertEquals( "running should be done.", 0, runner.getRunningCount() );
+		assertEquals( "waiting should be done.", 0, runner.getWaitingCount() );
+		assertTrue( runner.isDoneAndGone() );
+	    }
+	catch (InterruptedException e)
+	    {
+		e.printStackTrace();
+		fail("Unexpected InterruptedException: " + e);
+	    }
+    }
+
+    public void testBasicBehaviorFastSkipClose()
+    {
+	try
+	    {
+		DumbTask dt = new DumbTask();
+		for( int i = 0; i < 10; ++i )
+		    runner.postRunnable( dt );
+		runner.close( true );
+		Thread.sleep(1000); // not strictly safe, but should be plenty of time to interrupt and be done
+		assertTrue( runner.isDoneAndGone() );
+	    }
+	catch (InterruptedException e)
+	    {
+		e.printStackTrace();
+		fail("Unexpected InterruptedException: " + e);
+	    }
+    }
+
+    public void testDeadlockCase()
+    {
+	try
+	    {
+		runner.close(); //we need a different set up...
+		runner = new ThreadPerTaskAsynchronousRunner(5, 1000); //interrupt tasks after 1 sec, consider deadlocked after ~3 secs..
+		DumbTask dt = new DumbTask( true );
+		for( int i = 0; i < 5; ++i )
+		    runner.postRunnable( dt );
+		Thread.sleep(10000); // not strictly safe, but should be plenty of time to interrupt and be done
+		assertEquals( "running should be done.", 0, runner.getRunningCount() );
+	    }
+	catch (InterruptedException e)
+	    {
+		e.printStackTrace();
+		fail("Unexpected InterruptedException: " + e);
+	    }
+    }
+
+    
+
+    public void testDeadlockWithPentUpTasks()
+    {
+	try
+	    {
+		runner.close(); //we need a different set up...
+		runner = new ThreadPerTaskAsynchronousRunner(5, 1000); //interrupt tasks after 1 sec, consider deadlocked after ~3 secs..
+		//Runnable r = new Runnable() { public synchronized void run() { while (true) { try { this.wait();} catch (Exception e) {} } } };
+		Runnable r = new DumbTask( true );
+		Runnable r2 = new Runnable() { public void run() { System.out.println("done."); } };
+		for( int i = 0; i < 5; ++i )
+		    runner.postRunnable( r );
+		for( int i = 0; i < 5; ++i )
+		    runner.postRunnable( r2 );
+		Thread.sleep(10000); // not strictly safe, but should be plenty of time to interrupt and be done
+		assertEquals( "running should be done.", 0, runner.getRunningCount() );
+	    }
+	catch (InterruptedException e)
+	    {
+		e.printStackTrace();
+		fail("Unexpected InterruptedException: " + e);
+	    }
+    }
+
+    
+
+    class DumbTask implements Runnable
+    {
+	boolean ignore_interrupts;
+
+	DumbTask()
+	{ this( false ); }
+
+	DumbTask(boolean ignore_interrupts)
+	{ this.ignore_interrupts = ignore_interrupts; }
+
+	public void run()
+	{
+	    try
+		{
+		    synchronized (ThreadPerTaskAsynchronousRunnerJUnitTestCase.this)
+			{
+			    while (no_go)
+				{
+				    try { ThreadPerTaskAsynchronousRunnerJUnitTestCase.this.wait(); }
+				    catch (InterruptedException e)
+					{
+					    if (ignore_interrupts)
+						System.err.println(this + ": interrupt ignored!");
+					    else
+						{
+						    e.fillInStackTrace();
+						    throw e;
+						}
+					}
+				}
+			    //System.err.println( ++gone );
+			    ThreadPerTaskAsynchronousRunnerJUnitTestCase.this.notifyAll();
+			}
+		}
+	    catch ( Exception e )
+		{ e.printStackTrace(); }
+	}
+    }
+
+    public static void main(String[] argv)
+    { 
+	junit.textui.TestRunner.run( new TestSuite( ThreadPerTaskAsynchronousRunnerJUnitTestCase.class ) ); 
+	//junit.swingui.TestRunner.run( SqlUtilsJUnitTestCase.class ); 
+	//new SqlUtilsJUnitTestCase().testGoodDebugLoggingOfNestedExceptions();
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/async/junit/ThreadPoolAsynchronousRunnerJUnitTestCase.java b/src/classes/com/mchange/v2/async/junit/ThreadPoolAsynchronousRunnerJUnitTestCase.java
new file mode 100644
index 0000000..2466e83
--- /dev/null
+++ b/src/classes/com/mchange/v2/async/junit/ThreadPoolAsynchronousRunnerJUnitTestCase.java
@@ -0,0 +1,121 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.async.junit;
+
+import junit.framework.*;
+import com.mchange.v2.async.*;
+
+public class ThreadPoolAsynchronousRunnerJUnitTestCase extends TestCase
+{
+    ThreadPoolAsynchronousRunner runner;
+
+    boolean no_go = true;
+    int gone = 0;
+
+    protected void setUp() 
+    {
+	runner = new ThreadPoolAsynchronousRunner( 3,
+						   true,
+						   1000,
+						   3 * 1000,
+						   3 * 1000);
+    }
+
+    protected void tearDown() 
+    { 
+	runner.close(); 
+	go(); //get any interrupt ignorers going...
+    }
+
+    private synchronized void go()
+    {
+	no_go = false;
+	this.notifyAll();
+    }
+
+    public void testDeadlockCase()
+    {
+	try
+	    {
+		DumbTask dt = new DumbTask( true );
+		for( int i = 0; i < 5; ++i )
+		    runner.postRunnable( dt );
+		Thread.sleep(500);
+		assertEquals("we should have three running tasks", 3, runner.getActiveCount() );
+		assertEquals("we should have two pending tasks", 2, runner.getPendingTaskCount() );
+		Thread.sleep(10000); // not strictly safe, but should be plenty of time to interrupt and be done
+	    }
+	catch (InterruptedException e)
+	    {
+		e.printStackTrace();
+		fail("Unexpected InterruptedException: " + e);
+	    }
+    }
+
+    class DumbTask implements Runnable
+    {
+	boolean ignore_interrupts;
+
+	DumbTask()
+	{ this( false ); }
+
+	DumbTask(boolean ignore_interrupts)
+	{ this.ignore_interrupts = ignore_interrupts; }
+
+	public void run()
+	{
+	    try
+		{
+		    synchronized (ThreadPoolAsynchronousRunnerJUnitTestCase.this)
+			{
+			    while (no_go)
+				{
+				    try { ThreadPoolAsynchronousRunnerJUnitTestCase.this.wait(); }
+				    catch (InterruptedException e)
+					{
+					    if (ignore_interrupts)
+						System.err.println(this + ": interrupt ignored!");
+					    else
+						{
+						    e.fillInStackTrace();
+						    throw e;
+						}
+					}
+				}
+			    //System.err.println( ++gone );
+			    ThreadPoolAsynchronousRunnerJUnitTestCase.this.notifyAll();
+			}
+		}
+	    catch ( Exception e )
+		{ e.printStackTrace(); }
+	}
+    }
+
+    public static void main(String[] argv)
+    { 
+	junit.textui.TestRunner.run( new TestSuite( ThreadPoolAsynchronousRunnerJUnitTestCase.class ) ); 
+	//junit.swingui.TestRunner.run( SqlUtilsJUnitTestCase.class ); 
+	//new SqlUtilsJUnitTestCase().testGoodDebugLoggingOfNestedExceptions();
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/beans/BeansUtils.java b/src/classes/com/mchange/v2/beans/BeansUtils.java
new file mode 100644
index 0000000..7d67a8c
--- /dev/null
+++ b/src/classes/com/mchange/v2/beans/BeansUtils.java
@@ -0,0 +1,493 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.beans;
+
+import java.beans.*;
+import java.lang.reflect.*;
+import java.util.*;
+import com.mchange.v2.log.*;
+
+import com.mchange.v2.lang.Coerce;
+
+public final class BeansUtils
+{
+    final static MLogger logger = MLog.getLogger( BeansUtils.class );
+
+    final static Object[] EMPTY_ARGS = new Object[0];
+
+    public static PropertyEditor findPropertyEditor( PropertyDescriptor pd )
+    {
+        PropertyEditor out = null;
+        Class editorClass = null;
+        try
+        {
+            editorClass = pd.getPropertyEditorClass();
+            if (editorClass != null)
+                out = (PropertyEditor) editorClass.newInstance();
+        }
+        catch (Exception e)
+        {
+//          e.printStackTrace();
+//          System.err.println("WARNING: Bad property editor class " + editorClass.getName() + 
+//          " registered for property " + pd.getName());
+            if (logger.isLoggable( MLevel.WARNING ) )
+                logger.log(MLevel.WARNING, "Bad property editor class " + editorClass.getName() + " registered for property " + pd.getName(), e);
+        }
+
+        if ( out == null )
+            out = PropertyEditorManager.findEditor( pd.getPropertyType() );
+        return out;
+    }
+
+    public static boolean equalsByAccessibleProperties( Object bean0, Object bean1 )
+    throws IntrospectionException
+    { return equalsByAccessibleProperties( bean0, bean1, Collections.EMPTY_SET ); }
+
+    public static boolean equalsByAccessibleProperties( Object bean0, Object bean1, Collection ignoreProps )
+    throws IntrospectionException
+    {
+        Map m0 = new HashMap();
+        Map m1 = new HashMap();
+        extractAccessiblePropertiesToMap( m0, bean0, ignoreProps );
+        extractAccessiblePropertiesToMap( m1, bean1, ignoreProps );
+        //System.err.println("Map0 -> " + m0);
+        //System.err.println("Map1 -> " + m1);
+        return m0.equals(m1);
+    }
+
+    public static void overwriteAccessibleProperties( Object sourceBean, Object destBean )
+    throws IntrospectionException
+    { overwriteAccessibleProperties( sourceBean, destBean, Collections.EMPTY_SET ); }
+
+    public static void overwriteAccessibleProperties( Object sourceBean, Object destBean, Collection ignoreProps )
+    throws IntrospectionException
+    {
+        try
+        {
+            BeanInfo beanInfo = Introspector.getBeanInfo( sourceBean.getClass(), Object.class ); //so we don't see message about getClass()
+            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
+            for( int i = 0, len = pds.length; i < len; ++i)
+            {
+                PropertyDescriptor pd = pds[i];
+                if ( ignoreProps.contains( pd.getName() ) )
+                    continue;
+
+                Method getter = pd.getReadMethod();
+                Method setter = pd.getWriteMethod();
+
+                if ( getter == null || setter == null )
+                {
+                    if ( pd instanceof IndexedPropertyDescriptor )
+                    {
+//                      System.err.println("WARNING: BeansUtils.overwriteAccessibleProperties() does not");
+//                      System.err.println("support indexed properties that do not provide single-valued");
+//                      System.err.println("array getters and setters! [The indexed methods provide no means");
+//                      System.err.println("of modifying the size of the array in the destination bean if");
+//                      System.err.println("it does not match the source.]");
+
+                        if ( logger.isLoggable( MLevel.WARNING ) )
+                            logger.warning("BeansUtils.overwriteAccessibleProperties() does not" +
+                                            " support indexed properties that do not provide single-valued" +
+                                            " array getters and setters! [The indexed methods provide no means" +
+                                            " of modifying the size of the array in the destination bean if" +
+                            " it does not match the source.]");
+                    }
+
+                    //System.err.println("Property inaccessible for overwriting: " + pd.getName());
+                    if (logger.isLoggable( MLevel.INFO ))
+                        logger.info("Property inaccessible for overwriting: " + pd.getName());
+                }
+                else
+                {
+                    Object value = getter.invoke( sourceBean, EMPTY_ARGS );
+                    setter.invoke( destBean, new Object[] { value } );
+                }
+            }
+        }
+        catch ( IntrospectionException e )
+        { throw e; }
+        catch ( Exception e )
+        {
+            //e.printStackTrace();
+            if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED && logger.isLoggable( MLevel.FINE ))
+                logger.log( MLevel.FINE, "Converting exception to throwable IntrospectionException" );
+
+            throw new IntrospectionException( e.getMessage() );
+        }
+    }
+
+    public static void overwriteAccessiblePropertiesFromMap( Map sourceMap, Object destBean, boolean skip_nulls )
+    throws IntrospectionException
+    { overwriteAccessiblePropertiesFromMap( sourceMap, destBean, skip_nulls, Collections.EMPTY_SET ); }
+
+    public static void overwriteAccessiblePropertiesFromMap( Map sourceMap, Object destBean, boolean skip_nulls, Collection ignoreProps )
+    throws IntrospectionException
+    {
+        overwriteAccessiblePropertiesFromMap( sourceMap, 
+                        destBean, 
+                        skip_nulls, 
+                        ignoreProps, 
+                        false,
+                        MLevel.WARNING,
+                        MLevel.WARNING,
+                        true);
+    }
+
+    public static void overwriteAccessiblePropertiesFromMap( Map sourceMap, 
+                    Object destBean, 
+                    boolean skip_nulls, 
+                    Collection ignoreProps, 
+                    boolean coerce_strings,
+                    MLevel cantWriteLevel,
+                    MLevel cantCoerceLevel,
+                    boolean die_on_one_prop_failure)
+    throws IntrospectionException
+    {
+        if (cantWriteLevel == null)
+            cantWriteLevel = MLevel.WARNING;
+        if (cantCoerceLevel == null)
+            cantCoerceLevel = MLevel.WARNING;
+
+        Set sourceMapProps = sourceMap.keySet();
+
+        String propName = null;
+        BeanInfo beanInfo = Introspector.getBeanInfo( destBean.getClass(), Object.class ); //so we don't see message about getClass()
+        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
+        //System.err.println("ignoreProps: " + ignoreProps );
+        for( int i = 0, len = pds.length; i < len; ++i)
+        {
+            PropertyDescriptor pd = pds[i];
+            propName = pd.getName();
+
+            if (! sourceMapProps.contains( propName ))
+                continue;
+
+            if ( ignoreProps != null && ignoreProps.contains( propName ) )
+            {
+                //System.err.println("ignoring: " + propName);
+                continue;
+            }
+            //else
+            //    System.err.println("not ignoring: " + propName);
+
+            Object propVal = sourceMap.get( propName );
+            if (propVal == null)
+            {
+                if (skip_nulls) continue;
+                //do we need to worry about primitives here?
+            }
+
+            Method setter = pd.getWriteMethod();
+            boolean rethrow = false;
+
+            Class propType = pd.getPropertyType();;
+
+//          try
+//          {
+
+            if ( setter == null )
+            {
+                if ( pd instanceof IndexedPropertyDescriptor )
+                {
+                    if ( logger.isLoggable( MLevel.FINER ) )
+                        logger.finer("BeansUtils.overwriteAccessiblePropertiesFromMap() does not" +
+                                        " support indexed properties that do not provide single-valued" +
+                                        " array getters and setters! [The indexed methods provide no means" +
+                                        " of modifying the size of the array in the destination bean if" +
+                        " it does not match the source.]");
+
+                }
+
+                if ( logger.isLoggable( cantWriteLevel ))
+                {
+                    String msg = "Property inaccessible for overwriting: " + propName; 
+                    logger.log( cantWriteLevel, msg );
+                    if (die_on_one_prop_failure)
+                    {
+                        rethrow = true;
+                        throw new IntrospectionException( msg );
+                    }
+                }
+
+            }
+            else
+            {
+                if (coerce_strings &&
+                                propVal != null && 
+                                propVal.getClass() == String.class && 
+                                (propType = pd.getPropertyType()) != String.class &&
+                                Coerce.canCoerce( propType ))
+                {
+                    Object coercedPropVal;
+                    try
+                    { 
+                        coercedPropVal = Coerce.toObject( (String) propVal, propType ); 
+                        //System.err.println(propName + "-> coercedPropVal: " + coercedPropVal);
+                        setter.invoke( destBean, new Object[] { coercedPropVal } );
+                    }
+                    catch (IllegalArgumentException e) 
+                    {
+                        // thrown by Coerce.toObject()
+                        // recall that NumberFormatException inherits from IllegalArgumentException
+                        String msg = 
+                            "Failed to coerce property: " + propName +
+                            " [propVal: " + propVal + "; propType: " + propType + "]";
+                        if ( logger.isLoggable( cantCoerceLevel ) )
+                            logger.log( cantCoerceLevel, msg, e );
+                        if (die_on_one_prop_failure)
+                        {
+                            rethrow = true;
+                            throw new IntrospectionException( msg );
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        String msg = 
+                            "Failed to set property: " + propName +
+                            " [propVal: " + propVal + "; propType: " + propType + "]";
+                        if ( logger.isLoggable( cantWriteLevel ) )
+                            logger.log( cantWriteLevel, msg, e );
+                        if (die_on_one_prop_failure)
+                        {
+                            rethrow = true;
+                            throw new IntrospectionException( msg );
+                        }
+                    }
+                }
+                else
+                {
+                    try
+                    {
+                        //System.err.println("invoking method: " + setter);
+                        setter.invoke( destBean, new Object[] { propVal } );
+                    }
+                    catch (Exception e)
+                    {
+                        String msg = 
+                            "Failed to set property: " + propName +
+                            " [propVal: " + propVal + "; propType: " + propType + "]";
+                        if ( logger.isLoggable( cantWriteLevel ) )
+                            logger.log( cantWriteLevel, msg, e );
+                        if (die_on_one_prop_failure)
+                        {
+                            rethrow = true;
+                            throw new IntrospectionException( msg );
+                        }
+                    }
+                }
+            }
+//          }
+//          catch (Exception e)
+//          {
+//          if (e instanceof IntrospectionException && rethrow)
+//          throw (IntrospectionException) e;
+//          else
+//          {
+//          String msg = 
+//          "An exception occurred while trying to set property '" + propName +
+//          "' to value '" + propVal + "'. ";
+//          logger.log(MLevel.WARNING, msg, e);
+//          if (die_on_one_prop_failure)
+//          {
+//          rethrow = true;
+//          throw new IntrospectionException( msg + e.toString());
+//          }
+//          }
+//          }
+        }
+    }
+
+    public static void appendPropNamesAndValues(StringBuffer appendIntoMe, Object bean, Collection ignoreProps) throws IntrospectionException
+    {
+        Map tmp = new TreeMap( String.CASE_INSENSITIVE_ORDER );
+        extractAccessiblePropertiesToMap( tmp, bean, ignoreProps );
+        boolean first = true;
+        for (Iterator ii = tmp.keySet().iterator(); ii.hasNext(); )
+        {
+            String key = (String) ii.next();
+            Object val = tmp.get( key );
+            if (first)
+                first = false;
+            else
+                appendIntoMe.append( ", " );
+            appendIntoMe.append( key );
+            appendIntoMe.append( " -> ");
+            appendIntoMe.append( val );
+        }
+    }
+
+
+    public static void extractAccessiblePropertiesToMap( Map fillMe, Object bean ) throws IntrospectionException
+    { extractAccessiblePropertiesToMap( fillMe, bean, Collections.EMPTY_SET ); }
+
+    public static void extractAccessiblePropertiesToMap( Map fillMe, Object bean, Collection ignoreProps ) throws IntrospectionException
+    {
+        String propName = null;
+        try
+        {
+            BeanInfo bi = Introspector.getBeanInfo( bean.getClass(), Object.class );
+            PropertyDescriptor[] pds = bi.getPropertyDescriptors();
+            for (int i = 0, len = pds.length; i < len; ++i)
+            {
+                PropertyDescriptor pd = pds[i];
+                propName = pd.getName();
+                if (ignoreProps.contains( propName ))
+                    continue;
+
+                Method readMethod = pd.getReadMethod();
+                Object propVal = readMethod.invoke( bean, EMPTY_ARGS );
+                fillMe.put( propName, propVal );
+            }
+        }
+        catch ( IntrospectionException e )
+        {
+//          if (propName != null)
+//          System.err.println("Problem occurred while overwriting property: " + propName);
+            if ( logger.isLoggable( MLevel.WARNING ) )
+                logger.warning("Problem occurred while overwriting property: " + propName);
+            if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED && logger.isLoggable( MLevel.FINE ))
+                logger.logp( MLevel.FINE, 
+                                BeansUtils.class.getName(),
+                                "extractAccessiblePropertiesToMap( Map fillMe, Object bean, Collection ignoreProps )",
+                                (propName != null ? "Problem occurred while overwriting property: " + propName : "") + " throwing...",
+                                e );
+            throw e; 
+        }
+        catch ( Exception e )
+        {
+            //e.printStackTrace();
+            if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED && logger.isLoggable( MLevel.FINE ))
+                logger.logp( MLevel.FINE, 
+                                BeansUtils.class.getName(),
+                                "extractAccessiblePropertiesToMap( Map fillMe, Object bean, Collection ignoreProps )",
+                                "Caught unexpected Exception; Converting to IntrospectionException.",
+                                e );
+            throw new IntrospectionException( e.toString() + (propName == null ? "" : " [" + propName + ']') );
+        }
+    }
+
+    private static void overwriteProperty( String propName, Object value, Method putativeSetter, Object target )
+    throws Exception
+    {
+        if ( putativeSetter.getDeclaringClass().isAssignableFrom( target.getClass() ) )
+            putativeSetter.invoke( target, new Object[] { value } );
+        else
+        {
+            BeanInfo beanInfo = Introspector.getBeanInfo( target.getClass(), Object.class );
+            PropertyDescriptor pd = null;
+
+            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
+            for( int i = 0, len = pds.length; i < len; ++i)
+                if (propName.equals( pds[i].getName() ))
+                {
+                    pd = pds[i];
+                    break;
+                }
+
+            Method targetSetter = pd.getWriteMethod();
+            targetSetter.invoke( target, new Object[] { value } );
+        }
+    }
+
+
+    public static void overwriteSpecificAccessibleProperties( Object sourceBean, Object destBean, Collection props )
+    throws IntrospectionException
+    {
+        try
+        {
+            Set _props = new HashSet(props);
+
+            BeanInfo beanInfo = Introspector.getBeanInfo( sourceBean.getClass(), Object.class ); //so we don't see message about getClass()
+            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
+            for( int i = 0, len = pds.length; i < len; ++i)
+            {
+                PropertyDescriptor pd = pds[i];
+                String name = pd.getName();
+                if (! _props.remove( name ) )
+                    continue;
+
+                Method getter = pd.getReadMethod();
+                Method setter = pd.getWriteMethod();
+
+                if ( getter == null || setter == null )
+                {
+                    if ( pd instanceof IndexedPropertyDescriptor )
+                    {
+//                      System.err.println("WARNING: BeansUtils.overwriteAccessibleProperties() does not");
+//                      System.err.println("support indexed properties that do not provide single-valued");
+//                      System.err.println("array getters and setters! [The indexed methods provide no means");
+//                      System.err.println("of modifying the size of the array in the destination bean if");
+//                      System.err.println("it does not match the source.]");
+
+                        if ( logger.isLoggable( MLevel.WARNING ) )
+                            logger.warning("BeansUtils.overwriteAccessibleProperties() does not" +
+                                            " support indexed properties that do not provide single-valued" +
+                                            " array getters and setters! [The indexed methods provide no means" +
+                                            " of modifying the size of the array in the destination bean if" +
+                            " it does not match the source.]");
+                    }
+
+                    if ( logger.isLoggable( MLevel.INFO ) )
+                        logger.info("Property inaccessible for overwriting: " + pd.getName());
+                }
+                else
+                {
+                    Object value = getter.invoke( sourceBean, EMPTY_ARGS );
+                    overwriteProperty( name, value, setter, destBean );
+                    //setter.invoke( destBean, new Object[] { value } );
+                }
+            }
+            if ( logger.isLoggable( MLevel.WARNING ) )
+            {
+                for (Iterator ii = _props.iterator(); ii.hasNext(); )
+                    logger.warning("failed to find expected property: " + ii.next());
+                //System.err.println("failed to find expected property: " + ii.next());
+            }
+        }
+        catch ( IntrospectionException e )
+        { throw e; }
+        catch ( Exception e )
+        {
+            //e.printStackTrace();
+            if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED && logger.isLoggable( MLevel.FINE ))
+                logger.logp( MLevel.FINE, 
+                                BeansUtils.class.getName(),
+                                "overwriteSpecificAccessibleProperties( Object sourceBean, Object destBean, Collection props )",
+                                "Caught unexpected Exception; Converting to IntrospectionException.",
+                                e );
+            throw new IntrospectionException( e.getMessage() );
+        }
+    }
+
+    public static void debugShowPropertyChange( PropertyChangeEvent evt )
+    {
+        System.err.println("PropertyChangeEvent: [ propertyName -> " + evt.getPropertyName() + 
+                        ", oldValue -> " + evt.getOldValue() +
+                        ", newValue -> " + evt.getNewValue() +
+        " ]");
+    }
+
+    private BeansUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/beans/StateBean.java b/src/classes/com/mchange/v2/beans/StateBean.java
new file mode 100644
index 0000000..31d36a5
--- /dev/null
+++ b/src/classes/com/mchange/v2/beans/StateBean.java
@@ -0,0 +1,27 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.beans;
+
+public interface StateBean
+{}
diff --git a/src/classes/com/mchange/v2/beans/StateBeanExporter.java b/src/classes/com/mchange/v2/beans/StateBeanExporter.java
new file mode 100644
index 0000000..41becb3
--- /dev/null
+++ b/src/classes/com/mchange/v2/beans/StateBeanExporter.java
@@ -0,0 +1,34 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.beans;
+
+/**
+ * offers a bean suitable for bean getter/setter-based serialization/deserialization
+ * a la XMLSerializer. Should have a constructor that accepts the exported Object and
+ * constructs a new bean with the same state.
+ */
+public interface StateBeanExporter
+{
+    public StateBean exportStateBean();
+}
diff --git a/src/classes/com/mchange/v2/beans/StateBeanImporter.java b/src/classes/com/mchange/v2/beans/StateBeanImporter.java
new file mode 100644
index 0000000..76e24be
--- /dev/null
+++ b/src/classes/com/mchange/v2/beans/StateBeanImporter.java
@@ -0,0 +1,29 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.beans;
+
+public interface StateBeanImporter extends StateBeanExporter
+{
+    public void importStateBean(StateBean sb);
+}
diff --git a/src/classes/com/mchange/v2/c3p0/AbstractConnectionCustomizer.java b/src/classes/com/mchange/v2/c3p0/AbstractConnectionCustomizer.java
new file mode 100644
index 0000000..e1bf3ec
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/AbstractConnectionCustomizer.java
@@ -0,0 +1,51 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ *  An abstract implementation of the
+ *  ConnectionCustomizer interface
+ *  in which all methods are no-ops.
+ *
+ *  Just a convenience class since
+ *  most clients will only need to
+ *  implement a single method.
+ */
+public abstract class AbstractConnectionCustomizer implements ConnectionCustomizer
+{
+    public void onAcquire( Connection c, String parentDataSourceIdentityToken ) throws Exception
+    {}
+
+    public void onDestroy( Connection c, String parentDataSourceIdentityToken  ) throws Exception
+    {}
+
+    public void onCheckOut( Connection c, String parentDataSourceIdentityToken  ) throws Exception
+    {}
+
+    public void onCheckIn( Connection c, String parentDataSourceIdentityToken  ) throws Exception
+    {}
+}
diff --git a/src/classes/com/mchange/v2/c3p0/AbstractConnectionTester.java b/src/classes/com/mchange/v2/c3p0/AbstractConnectionTester.java
new file mode 100644
index 0000000..b847253
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/AbstractConnectionTester.java
@@ -0,0 +1,83 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.Connection;
+
+/**
+ *  <p>Having expanded the once-simple ConnectionTester interface to support both
+ *  user-specified queries and return of root cause Exceptions (via an out-param),
+ *  this interface has grown unnecessarily complex.</p>
+ *  
+ *  <p>If you wish to implement a custom Connection tester, here is the simple
+ *  way to do it</p>
+ *  
+ *  <ol>
+ *    <li>Extend {@link com.mchange.v2.c3p0.AbstractConnectionTester}</li>
+ *    <li>Override only the two abstract methods</li>
+ *    <ul>
+ *       <li><tt>public int activeCheckConnection(Connection c, String preferredTestQuery, Throwable[] rootCauseOutParamHolder)</tt></li>
+ *       <li><tt>public int statusOnException(Connection c, Throwable t, String preferredTestQuery, Throwable[] rootCauseOutParamHolder)</tt></li>
+ *    </ul>
+ *    <li>Take care to ensure that your methods are defined to allow <tt>preferredTestQuery</tt> and 
+ *    <tt>rootCauseOutParamHolder</tt> to be <tt>null</tt>.</li>
+ *  </ol>
+ *  
+ *  <p>Parameter <tt>rootCauseOutParamHolder</tt> is an optional parameter, which if supplied, will be a Throwable array whose size
+ *  it at least one. If a Connection test fails because of some Exception, the Connection tester may set this Exception as the
+ *  zero-th element of the array to provide information about why and how the test failed.</p> 
+ */
+public abstract class AbstractConnectionTester implements UnifiedConnectionTester
+{
+    /**
+     *  Override, but remember that <tt>preferredTestQuery</tt> and <tt>rootCauseOutParamHolder</tt>
+     *  can be null.
+     */
+    public abstract int activeCheckConnection(Connection c, String preferredTestQuery, Throwable[] rootCauseOutParamHolder);
+
+    /**
+     *  Override, but remember that <tt>preferredTestQuery</tt> and <tt>rootCauseOutParamHolder</tt>
+     *  can be null.
+     */
+    public abstract int statusOnException(Connection c, Throwable t, String preferredTestQuery, Throwable[] rootCauseOutParamHolder);
+
+    //usually just leave the rest of these as-is
+    public int activeCheckConnection(Connection c)
+    { return activeCheckConnection( c, null, null); }
+
+    public int activeCheckConnection(Connection c, Throwable[] rootCauseOutParamHolder)
+    { return activeCheckConnection( c, null, rootCauseOutParamHolder); }
+
+    public int activeCheckConnection(Connection c, String preferredTestQuery)
+    { return activeCheckConnection( c, preferredTestQuery, null); }
+
+    public int statusOnException(Connection c, Throwable t)
+    { return statusOnException( c, t, null, null); }
+
+    public int statusOnException(Connection c, Throwable t, Throwable[] rootCauseOutParamHolder)
+    { return statusOnException( c, t, null, rootCauseOutParamHolder); }
+
+    public int statusOnException(Connection c, Throwable t, String preferredTestQuery)
+    { return statusOnException( c, t, preferredTestQuery, null); }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/C3P0ProxyConnection.java b/src/classes/com/mchange/v2/c3p0/C3P0ProxyConnection.java
new file mode 100644
index 0000000..a97ef57
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/C3P0ProxyConnection.java
@@ -0,0 +1,83 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ *  <p><b>Most clients need never use or know about this interface -- c3p0-provided Connections
+ *  can be treated like any other Connection.</b></p>
+ *
+ *  <p>An interface implemented by proxy Connections returned
+ *  by c3p0 PooledDataSources. It provides protected access to the underlying
+ *  dbms-vendor specific Connection, which may be useful if you want to
+ *  access non-standard API offered by your jdbc driver.
+ */
+public interface C3P0ProxyConnection extends Connection
+{
+    /**
+     *  A token representing an unwrapped, unproxied jdbc Connection
+     *  for use in {@link #rawConnectionOperation}
+     */
+    public final static Object RAW_CONNECTION = new Object();
+    
+    /**
+     *  <p>Allows one to work with the unproxied, raw Connection. Some 
+     *  database companies never got over the "common interfaces mean
+     *  no more vendor lock-in!" thing, and offer non-standard API
+     *  on their Connections. This method permits you to "pierce" the
+     *  connection-pooling layer to call non-standard methods on the
+     *  original Connection, or to pass the original Connections to 
+     *  functions that are not implementation neutral.</p>
+     *
+     *  <p>To use this functionality, you'll need to cast a Connection
+     *  retrieved from a c3p0 PooledDataSource to a 
+     *  C3P0ProxyConnection.</p>
+     *
+     *  <p>This method works by making a reflective call of method <tt>m</tt> on
+     *  Object <tt>target</tt> (which may be null for static methods), passing
+     *  and argument list <tt>args</tt>. For the method target, or for any argument,
+     *  you may substitute the special token <tt>C3P0ProxyConnection.RAW_CONNECTION</tt></p>
+     *
+     *  <p>Any Statements or ResultSets returned by the operation will be proxied
+     *  and c3p0-managed, meaning that these resources will be automatically closed 
+     *  if the user does not close them first when this Connection is checked back
+     *  into the pool. <b>Any other resources returned by the operation are the user's
+     *  responsibility to clean up!</b></p>
+     *
+     *  <p>Incautious use of this method can corrupt the Connection pool, by breaking the invariant
+     *  that all checked-in Connections should be equivalent. If your vendor supplies API
+     *  that allows you to modify the state or configuration of a Connection in some nonstandard way,
+     *  you might use this method to do so, and then check the Connection back into the pool.
+     *  When you fetch another Connection from the PooledDataSource, it will be undefined
+     *  whether the Connection returned will have your altered configuration, or the default
+     *  configuration of a "fresh" Connection. Thus, it is inadvisable to use this method to call
+     *  nonstandard mutators. 
+     */
+    public Object rawConnectionOperation(Method m, Object target, Object[] args)
+	throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SQLException;
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/C3P0ProxyStatement.java b/src/classes/com/mchange/v2/c3p0/C3P0ProxyStatement.java
new file mode 100644
index 0000000..fe4ac6c
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/C3P0ProxyStatement.java
@@ -0,0 +1,84 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.Statement;
+import java.sql.SQLException;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ *  <p><b>Most clients need never use or know about this interface -- c3p0-provided Statements
+ *  can be treated like any other Statement.</b></p>
+ *
+ *  <p>An interface implemented by proxy Connections returned
+ *  by c3p0 PooledDataSources. It provides protected access to the underlying
+ *  dbms-vendor specific Connection, which may be useful if you want to
+ *  access non-standard API offered by your jdbc driver.
+ */
+public interface C3P0ProxyStatement extends Statement
+{
+    /**
+     *  A token representing an unwrapped, unproxied jdbc Connection
+     *  for use in {@link #rawStatementOperation}
+     */
+    public final static Object RAW_STATEMENT = new Object();
+    
+    /**
+     *  <p>Allows one to work with the unproxied, raw vendor-provided Statement . Some 
+     *  database companies never got over the "common interfaces mean
+     *  no more vendor lock-in!" thing, and offer non-standard API
+     *  on their Statements. This method permits you to "pierce" the
+     *  connection-pooling layer to call non-standard methods on the
+     *  original Statement, or to pass the original Statement to 
+     *  functions that are not implementation neutral.</p>
+     *
+     *  <p>To use this functionality, you'll need to cast a Statement
+     *  retrieved from a c3p0-provided Connection to a 
+     *  C3P0ProxyStatement.</p>
+     *
+     *  <p>This method works by making a reflective call of method <tt>m</tt> on
+     *  Object <tt>target</tt> (which may be null for static methods), passing
+     *  and argument list <tt>args</tt>. For the method target, or for any argument,
+     *  you may substitute the special token <tt>C3P0ProxyStatement.RAW_STATEMENT</tt></p>
+     *
+     *  <p>Any ResultSets returned by the operation will be proxied
+     *  and c3p0-managed, meaning that these resources will be automatically closed 
+     *  if the user does not close them first when this Statement is closed or checked
+     *  into the statement cache. <b>Any other resources returned by the operation are the user's
+     *  responsibility to clean up!</b></p>
+     *
+     *  <p>If you have turned statement pooling on, incautious use of this method can corrupt the 
+     *  PreparedStatement cache, by breaking the invariant
+     *  that all cached PreparedStatements should be equivalent to a PreparedStatement newly created
+     *  with the same arguments to prepareStatement(...) or prepareCall(...). If your vendor supplies API
+     *  that allows you to modify the state or configuration of a Statement in some nonstandard way,
+     *  and you do not undo this modification prior to closing the Statement or the Connection that
+     *  prepared it, future preparers of the same Statement may or may not see your modification,
+     *  depending on your use of the cache. Thus, it is inadvisable to use this method to call
+     *  nonstandard mutators on PreparedStatements if statement pooling is turned on.. 
+     */
+    public Object rawStatementOperation(Method m, Object target, Object[] args)
+	throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SQLException;
+}
diff --git a/src/classes/com/mchange/v2/c3p0/C3P0Registry.java b/src/classes/com/mchange/v2/c3p0/C3P0Registry.java
new file mode 100644
index 0000000..eeeb1c0
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/C3P0Registry.java
@@ -0,0 +1,339 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.util.*;
+import com.mchange.v2.coalesce.*;
+import com.mchange.v2.log.*;
+import com.mchange.v2.c3p0.cfg.C3P0ConfigUtils;
+import com.mchange.v2.c3p0.impl.*;
+
+import java.sql.SQLException;
+import com.mchange.v2.c3p0.impl.IdentityTokenized;
+import com.mchange.v2.c3p0.subst.C3P0Substitutions;
+import com.mchange.v2.sql.SqlUtils;
+import com.mchange.v2.util.DoubleWeakHashMap;
+
+import com.mchange.v2.c3p0.management.*;
+
+/*
+ *  The primary purpose of C3P0Registry is to maintain a mapping of "identityTokens"
+ *  to c3p0 DataSources so that if the same DataSource is looked up (and deserialized
+ *  or dereferenced) via JNDI, c3p0 can ensure that the same instance is always returned.
+ *  But there are subtle issues here. If C3P0Registry maintains hard references to
+ *  DataSources, then they can never be garbage collected. But if c3p0 retains only
+ *  weak references, then applications that look up DataSources, then dereference them,
+ *  and then re-look them up again (not a great idea, but not uncommon) might see
+ *  distinct DataSources over multiple lookups.
+ *
+ *  C3P0 resolves this issue has followed: At first creation or lookup of a PooledDataSource, 
+ *  c3p0 creates a hard reference to that DataSource. So long as the DataSource has not
+ *  been close()ed or DataSources.destroy()ed, subsequent lookups will consistently
+ *  return the same DataSource. If the DataSource is never closed, then there is a potential
+ *  memory leak (as well as the potential Thread leak and Connection leak). But if
+ *  the DataSource is close()ed, only weak refernces to the DataSource will be retained.
+ *  A lookup of a DataSource after it has been close()ed within the current VM may
+ *  return the previously close()ed instance, or may return a fresh instance, depending
+ *  on whether the weak reference has been cleared. In other words, the result of
+ *  looking up a DataSource after having close()ed it in the current VM is undefined.
+ *
+ *  Note that unpooled c3p0 DataSources are always held by weak references, since
+ *  they are never explicitly close()ed. The result of looking up an unpooled DataSource, 
+ *  modifying it, dereferencing it, and then relooking up is therefore undefined as well.
+ *
+ *  These issues are mostly academic. Under normal use scenarios, how c3p0 deals with
+ *  maintaining its registry doesn't much matter. In the past, c3p0 maintained hard
+ *  references to DataSources indefinitely. At least one user ran into side effects
+ *  of the unwanted retention of old DataSources (in a process left to run for months
+ *  at a time, and frequently reconstructing multiple DataSources), so now we take care 
+ *  to ensure that when users properly close() and dereference DataSources, they can 
+ *  indeed be garbage collected.
+ */
+public final class C3P0Registry
+{
+    private final static String MC_PARAM = "com.mchange.v2.c3p0.management.ManagementCoordinator";
+    
+    //MT: thread-safe
+    final static MLogger logger = MLog.getLogger( C3P0Registry.class );
+
+    //MT: protected by class' lock
+    static boolean banner_printed = false;
+    
+    //MT: protected by class' lock
+    static boolean registry_mbean_registered = false;
+
+    //MT: thread-safe, immutable
+    private static CoalesceChecker CC = IdentityTokenizedCoalesceChecker.INSTANCE;
+
+    //MT: protected by class' lock
+    //a weak, unsynchronized coalescer
+    private static Coalescer idtCoalescer = CoalescerFactory.createCoalescer(CC, true , false);
+
+    //MT: protected by class' lock
+    private static Map tokensToTokenized = new DoubleWeakHashMap();
+
+    //MT: protected by class' lock
+    private static HashSet unclosedPooledDataSources = new HashSet();
+
+    //MT: protected by its own lock
+    private static Map classNamesToConnectionTesters = Collections.synchronizedMap( new HashMap() );
+
+    //MT: protected by its own lock
+    private static Map classNamesToConnectionCustomizers = Collections.synchronizedMap( new HashMap() );
+
+    private static ManagementCoordinator mc;
+
+    static
+    {
+        classNamesToConnectionTesters.put(C3P0Defaults.connectionTesterClassName(), C3P0Defaults.connectionTester());
+
+        String userManagementCoordinator = C3P0ConfigUtils.getPropFileConfigProperty(MC_PARAM);
+        if (userManagementCoordinator != null)
+        {
+            try
+            {
+                mc = (ManagementCoordinator) Class.forName(userManagementCoordinator).newInstance();
+            }
+            catch (Exception e)
+            {
+                if (logger.isLoggable(MLevel.WARNING))
+                    logger.log(MLevel.WARNING, 
+                               "Could not instantiate user-specified ManagementCoordinator " + userManagementCoordinator +
+                               ". Using NullManagementCoordinator (c3p0 JMX management disabled!)",
+                               e );
+                mc = new NullManagementCoordinator();
+            }
+        }
+        else
+        {    
+            try
+            {
+                Class.forName("java.lang.management.ManagementFactory");
+
+                mc = (ManagementCoordinator) Class.forName( "com.mchange.v2.c3p0.management.ActiveManagementCoordinator" ).newInstance();
+            }
+            catch (Exception e)
+            {
+                if ( logger.isLoggable( MLevel.INFO ) )
+                    logger.log( MLevel.INFO, 
+                                    "jdk1.5 management interfaces unavailable... JMX support disabled.",
+                                    e);
+                mc = new NullManagementCoordinator();
+            }
+        }
+    }
+
+    public static ConnectionTester getConnectionTester( String className )
+    {
+        try
+        {
+            ConnectionTester out = (ConnectionTester) classNamesToConnectionTesters.get( className );
+            if (out == null)
+            { 
+                out = (ConnectionTester) Class.forName( className ).newInstance();
+                classNamesToConnectionTesters.put( className, out );
+            }
+            return out;
+        }
+        catch (Exception e)
+        {
+            if (logger.isLoggable( MLevel.WARNING ))
+                logger.log( MLevel.WARNING, 
+                                "Could not create for find ConnectionTester with class name '" +
+                                className + "'. Using default.",
+                                e );
+            return C3P0Defaults.connectionTester();
+        }
+    }
+
+    public static ConnectionCustomizer getConnectionCustomizer( String className ) throws SQLException
+    {
+        if ( className == null )
+            return null;
+        else
+        {
+            try
+            {
+                ConnectionCustomizer out = (ConnectionCustomizer) classNamesToConnectionCustomizers.get( className );
+                if (out == null)
+                { 
+                    out = (ConnectionCustomizer) Class.forName( className ).newInstance();
+                    classNamesToConnectionCustomizers.put( className, out );
+                }
+                return out;
+            }
+            catch (Exception e)
+            {
+                if (logger.isLoggable( MLevel.WARNING ))
+                    logger.log( MLevel.WARNING, 
+                                    "Could not create for find ConnectionCustomizer with class name '" +
+                                    className + "'.",
+                                    e );
+                throw SqlUtils.toSQLException( e );
+            }
+        }
+    }
+
+    // must be called from a static sync'ed method
+    private static void banner()
+    {
+        if (! banner_printed )
+        {
+            if (logger.isLoggable( MLevel.INFO ) )
+                logger.info("Initializing c3p0-" + C3P0Substitutions.VERSION + " [built " + C3P0Substitutions.TIMESTAMP + 
+                                "; debug? " + C3P0Substitutions.DEBUG + 
+                                "; trace: " + C3P0Substitutions.TRACE 
+                                +']');
+            banner_printed = true;
+        }
+    }
+    
+    // must be called from a static, sync'ed method
+    private static void attemptRegisterRegistryMBean()
+    {
+        if (! registry_mbean_registered)
+        {
+            mc.attemptManageC3P0Registry();
+            registry_mbean_registered = true;
+        }
+    }
+
+    // must be called with class' lock
+    private static boolean isIncorporated( IdentityTokenized idt )
+    { return tokensToTokenized.keySet().contains( idt.getIdentityToken() ); }
+
+    // must be called with class' lock
+    private static void incorporate( IdentityTokenized idt )
+    {
+        tokensToTokenized.put( idt.getIdentityToken(), idt );
+        if (idt instanceof PooledDataSource)
+        {
+            unclosedPooledDataSources.add( idt );
+            mc.attemptManagePooledDataSource( (PooledDataSource) idt );
+        }
+    }
+
+    public static synchronized IdentityTokenized reregister(IdentityTokenized idt)
+    {
+        if (idt instanceof PooledDataSource)
+        {
+            banner();
+            attemptRegisterRegistryMBean();
+        }
+        
+        if (idt.getIdentityToken() == null)
+            throw new RuntimeException("[c3p0 issue] The identityToken of a registered object should be set prior to registration.");
+
+        IdentityTokenized coalesceCheck = (IdentityTokenized) idtCoalescer.coalesce(idt);
+
+        if (! isIncorporated( coalesceCheck ))
+            incorporate( coalesceCheck );
+
+        return coalesceCheck;
+    }
+    
+    public static synchronized void markClosed(PooledDataSource pds)
+    {
+        unclosedPooledDataSources.remove(pds);
+        mc.attemptUnmanagePooledDataSource( pds );
+        if (unclosedPooledDataSources.isEmpty())
+        {
+            mc.attemptUnmanageC3P0Registry();
+            registry_mbean_registered = false;
+        }   
+    }
+
+    public synchronized static Set getPooledDataSources()
+    { return (Set) unclosedPooledDataSources.clone(); }
+
+    public synchronized static Set pooledDataSourcesByName( String dataSourceName )
+    {
+        Set out = new HashSet();
+        for (Iterator ii = unclosedPooledDataSources.iterator(); ii.hasNext(); )
+        {
+            PooledDataSource pds = (PooledDataSource) ii.next();
+            if ( pds.getDataSourceName().equals( dataSourceName ) )
+                out.add( pds );
+        }
+        return out;
+    }
+
+    public synchronized static PooledDataSource pooledDataSourceByName( String dataSourceName )
+    {
+        for (Iterator ii = unclosedPooledDataSources.iterator(); ii.hasNext(); )
+        {
+            PooledDataSource pds = (PooledDataSource) ii.next();
+            if ( pds.getDataSourceName().equals( dataSourceName ) )
+                return pds;
+        }
+        return null;
+    }
+
+    public synchronized static Set allIdentityTokens()
+    { 
+        Set out = Collections.unmodifiableSet( tokensToTokenized.keySet() ); 
+        //System.err.println( "allIdentityTokens(): " + out );
+        return out;
+    }
+
+    public synchronized static Set allIdentityTokenized()
+    { 
+        HashSet out = new HashSet();
+        out.addAll( tokensToTokenized.values() );
+        //System.err.println( "allIdentityTokenized(): " + out );
+        return Collections.unmodifiableSet( out );
+    }
+
+    public synchronized static Set allPooledDataSources()
+    { 
+        Set out = Collections.unmodifiableSet( unclosedPooledDataSources ); 
+        //System.err.println( "allPooledDataSources(): " + out );
+        return out;
+    }
+
+    public synchronized static int getNumPooledDataSources()
+    { return unclosedPooledDataSources.size(); }
+
+    public synchronized static int getNumPoolsAllDataSources() throws SQLException
+    {
+	int count = 0; 
+	for (Iterator ii = unclosedPooledDataSources.iterator(); ii.hasNext();) 
+	    { 
+		PooledDataSource pds = (PooledDataSource) ii.next(); 
+		count += pds.getNumUserPools(); 
+	    } 
+	return count; 
+    }
+
+    public synchronized int getNumThreadsAllThreadPools() throws SQLException
+    {
+	int count = 0; 
+	for (Iterator ii = unclosedPooledDataSources.iterator(); ii.hasNext();) 
+	    { 
+		PooledDataSource pds = (PooledDataSource) ii.next(); 
+		count += pds.getNumHelperThreads(); 
+	    } 
+	return count; 
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/ComboPooledDataSource.java b/src/classes/com/mchange/v2/c3p0/ComboPooledDataSource.java
new file mode 100644
index 0000000..970c3cb
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/ComboPooledDataSource.java
@@ -0,0 +1,684 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.beans.*;
+import java.io.*;
+import java.sql.*;
+import java.util.*;
+import javax.naming.*;
+import com.mchange.v2.log.*;
+import com.mchange.v2.naming.*;
+import com.mchange.v2.c3p0.impl.*;
+
+import javax.sql.DataSource;
+import com.mchange.v2.beans.BeansUtils;
+import com.mchange.v2.c3p0.cfg.C3P0Config;
+
+/**
+ * <p>For the meaning of most of these properties, please see c3p0's top-level documentation!</p>
+ */
+public final class ComboPooledDataSource extends AbstractPoolBackedDataSource implements PooledDataSource, Serializable, Referenceable
+{
+    final static MLogger logger = MLog.getLogger( ComboPooledDataSource.class );
+
+    final static Set TO_STRING_IGNORE_PROPS = new HashSet( Arrays.asList( new String[] { 
+                    "connection",
+                    "lastAcquisitionFailureDefaultUser",
+                    "lastCheckinFailureDefaultUser",
+                    "lastCheckoutFailureDefaultUser",
+                    "lastConnectionTestFailureDefaultUser",
+                    "lastIdleTestFailureDefaultUser",
+                    "logWriter",
+                    "loginTimeout",
+                    "numBusyConnections",
+                    "numBusyConnectionsAllUsers",
+                    "numBusyConnectionsDefaultUser",
+                    "numConnections",
+                    "numConnectionsAllUsers",
+                    "numConnectionsDefaultUser",
+                    "numFailedCheckinsDefaultUser",
+                    "numFailedCheckoutsDefaultUser",
+                    "numFailedIdleTestsDefaultUser",
+                    "numIdleConnections",
+                    "numIdleConnectionsAllUsers",
+                    "numIdleConnectionsDefaultUser",
+                    "numUnclosedOrphanedConnections",
+                    "numUnclosedOrphanedConnectionsAllUsers",
+                    "numUnclosedOrphanedConnectionsDefaultUser",
+                    "numUserPools",
+                    "effectivePropertyCycleDefaultUser",
+                    "startTimeMillisDefaultUser",
+                    "statementCacheNumCheckedOutDefaultUser",
+                    "statementCacheNumCheckedOutStatementsAllUsers",
+                    "statementCacheNumConnectionsWithCachedStatementsAllUsers",
+                    "statementCacheNumConnectionsWithCachedStatementsDefaultUser",
+                    "statementCacheNumStatementsAllUsers",
+                    "statementCacheNumStatementsDefaultUser",
+                    "threadPoolSize",
+                    "threadPoolNumActiveThreads",
+                    "threadPoolNumIdleThreads",
+                    "threadPoolNumTasksPending",
+                    "threadPoolStackTraces",
+                    "threadPoolStatus",
+                    "overrideDefaultUser",
+                    "overrideDefaultPassword",
+                    "password",
+                    "reference",
+                    "upTimeMillisDefaultUser",
+                    "user",
+                    "userOverridesAsString",
+                    "allUsers",
+                    "connectionPoolDataSource"
+    } ) );
+
+    // not reassigned post-ctor; mutable elements protected by their own locks
+    // when (very rarely) necessery, we sync this -> wcpds -> dmds
+
+    // note that serialization of these guys happens via out superclass
+    // we just have to make sure they get properly reset on deserialization
+    transient DriverManagerDataSource         dmds;
+    transient WrapperConnectionPoolDataSource wcpds;
+
+    public ComboPooledDataSource()
+    { this( true ); }
+
+    public ComboPooledDataSource( boolean autoregister )
+    {
+        super( autoregister );
+
+        // System.err.println("...Initializing ComboPooledDataSource.");
+
+        dmds  = new DriverManagerDataSource();
+        wcpds = new WrapperConnectionPoolDataSource();
+
+        wcpds.setNestedDataSource( dmds );
+
+        try
+        { this.setConnectionPoolDataSource( wcpds ); }
+        catch (PropertyVetoException e)
+        {
+            logger.log(MLevel.WARNING, "Hunh??? This can't happen. We haven't set up any listeners to veto the property change yet!", e);
+            throw new RuntimeException("Hunh??? This can't happen. We haven't set up any listeners to veto the property change yet! " + e);
+        }
+
+        // set things up in case there are future changes to our ConnectionPoolDataSource
+        //
+        setUpPropertyEvents();
+    }
+
+    private void setUpPropertyEvents()
+    {
+        VetoableChangeListener wcpdsConsistencyEnforcer = new VetoableChangeListener()
+        {
+            // always called within synchronized mutators of the parent class... needn't explicitly sync here
+            public void vetoableChange( PropertyChangeEvent evt ) throws PropertyVetoException
+            {
+                String propName = evt.getPropertyName();
+                Object val = evt.getNewValue();
+
+                if ( "connectionPoolDataSource".equals( propName ) )
+                {
+                    if (val instanceof WrapperConnectionPoolDataSource)
+                    {
+                        DataSource nested = (DataSource) ((WrapperConnectionPoolDataSource)val).getNestedDataSource();
+                        if (! (nested instanceof DriverManagerDataSource) )
+                            throw new PropertyVetoException("ComboPooledDataSource requires that its unpooled DataSource " +
+                                            " be set at all times, and that it be a" +
+                                            " com.mchange.v2.c3p0.DriverManagerDataSource. Bad: " + nested, evt);
+                    }
+                    else
+                        throw new PropertyVetoException("ComboPooledDataSource requires that its ConnectionPoolDataSource " +
+                                        " be set at all times, and that it be a" +
+                                        " com.mchange.v2.c3p0.WrapperConnectionPoolDataSource. Bad: " + val, evt);
+                }
+            }
+        };
+        this.addVetoableChangeListener( wcpdsConsistencyEnforcer );
+
+        PropertyChangeListener wcpdsStateUpdater = new PropertyChangeListener()
+        {
+            public void propertyChange( PropertyChangeEvent evt )
+            { updateLocalVarsFromCpdsProp(); }
+        };
+        this.addPropertyChangeListener( wcpdsStateUpdater );
+    }
+
+    private void updateLocalVarsFromCpdsProp()
+    {
+        this.wcpds = (WrapperConnectionPoolDataSource) this.getConnectionPoolDataSource();
+        this.dmds  = (DriverManagerDataSource) wcpds.getNestedDataSource();
+    }
+
+    public ComboPooledDataSource(String configName)
+    { 
+        this();
+        initializeNamedConfig( configName );
+    }
+
+//  // workaround sun big id #6342411 (in which reflective
+//  // access to a public method of a non-public class fails,
+//  // even if the non-public class is accessed via a public
+//  // subclass)
+//  public String getDataSourceName()
+//  { return super.getDataSourceName(); }
+
+    // DriverManagerDataSourceProperties  (count: 4)
+    public String getDescription()
+    { return dmds.getDescription(); }
+
+    public void setDescription( String description )
+    { dmds.setDescription( description ); }
+
+    public String getDriverClass()
+    { return dmds.getDriverClass(); }
+
+    public void setDriverClass( String driverClass ) throws PropertyVetoException
+    { 
+        dmds.setDriverClass( driverClass ); 
+//      System.err.println("setting driverClass: " + driverClass); 
+    }
+
+    public String getJdbcUrl()
+    {  
+//      System.err.println("getting jdbcUrl: " + dmds.getJdbcUrl()); 
+        return dmds.getJdbcUrl(); 
+    }
+
+    public void setJdbcUrl( String jdbcUrl )
+    { 
+        dmds.setJdbcUrl( jdbcUrl ); 
+        this.resetPoolManager( false );
+//      System.err.println("setting jdbcUrl: " + jdbcUrl + " [dmds@" + C3P0ImplUtils.identityToken( dmds ) + "]"); 
+//      if (jdbcUrl == null)
+//      new Exception("*** NULL SETTER ***").printStackTrace();
+    }
+
+    public Properties getProperties()
+    { 
+        //System.err.println("getting properties: " + dmds.getProperties()); 
+        return dmds.getProperties(); 
+    }
+
+    public void setProperties( Properties properties )
+    { 
+        //System.err.println("setting properties: " + properties); 
+        dmds.setProperties( properties ); 
+        this.resetPoolManager(false);
+    }
+
+    // DriverManagerDataSource "virtual properties" based on properties
+    public String getUser()
+    { return dmds.getUser(); }
+
+    public void setUser( String user )
+    { 
+        dmds.setUser( user ); 
+        this.resetPoolManager( false );
+    }
+
+    public String getPassword()
+    { return dmds.getPassword(); }
+
+    public void setPassword( String password )
+    { 
+        dmds.setPassword( password ); 
+        this.resetPoolManager( false );
+    }
+
+    // WrapperConnectionPoolDataSource properties
+    public int getCheckoutTimeout()
+    { return wcpds.getCheckoutTimeout(); }
+
+    public void setCheckoutTimeout( int checkoutTimeout )
+    { 
+        wcpds.setCheckoutTimeout( checkoutTimeout ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getAcquireIncrement()
+    { return wcpds.getAcquireIncrement(); }
+
+    public void setAcquireIncrement( int acquireIncrement )
+    { 
+        wcpds.setAcquireIncrement( acquireIncrement ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getAcquireRetryAttempts()
+    { return wcpds.getAcquireRetryAttempts(); }
+
+    public void setAcquireRetryAttempts( int acquireRetryAttempts )
+    { 
+        wcpds.setAcquireRetryAttempts( acquireRetryAttempts ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getAcquireRetryDelay()
+    { return wcpds.getAcquireRetryDelay(); }
+
+    public void setAcquireRetryDelay( int acquireRetryDelay )
+    { 
+        wcpds.setAcquireRetryDelay( acquireRetryDelay ); 
+        this.resetPoolManager( false );
+    }
+
+    public boolean isAutoCommitOnClose()
+    { return wcpds.isAutoCommitOnClose(); }
+
+    public void setAutoCommitOnClose( boolean autoCommitOnClose )
+    { 
+        wcpds.setAutoCommitOnClose( autoCommitOnClose ); 
+        this.resetPoolManager( false );
+    }
+
+    public String getConnectionTesterClassName()
+    { return wcpds.getConnectionTesterClassName(); }
+
+    public void setConnectionTesterClassName( String connectionTesterClassName ) throws PropertyVetoException
+    { 
+        wcpds.setConnectionTesterClassName( connectionTesterClassName ); 
+        this.resetPoolManager( false );
+    }
+
+    public String getAutomaticTestTable()
+    { return wcpds.getAutomaticTestTable(); }
+
+    public void setAutomaticTestTable( String automaticTestTable )
+    { 
+        wcpds.setAutomaticTestTable( automaticTestTable ); 
+        this.resetPoolManager( false );
+    }
+
+    public boolean isForceIgnoreUnresolvedTransactions()
+    { return wcpds.isForceIgnoreUnresolvedTransactions(); }
+
+    public void setForceIgnoreUnresolvedTransactions( boolean forceIgnoreUnresolvedTransactions )
+    { 
+        wcpds.setForceIgnoreUnresolvedTransactions( forceIgnoreUnresolvedTransactions ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getIdleConnectionTestPeriod()
+    { return wcpds.getIdleConnectionTestPeriod(); }
+
+    public void setIdleConnectionTestPeriod( int idleConnectionTestPeriod )
+    { 
+        wcpds.setIdleConnectionTestPeriod( idleConnectionTestPeriod ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getInitialPoolSize()
+    { return wcpds.getInitialPoolSize(); }
+
+    public void setInitialPoolSize( int initialPoolSize )
+    { 
+        wcpds.setInitialPoolSize( initialPoolSize ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getMaxIdleTime()
+    { return wcpds.getMaxIdleTime(); }
+
+    public void setMaxIdleTime( int maxIdleTime )
+    { 
+        wcpds.setMaxIdleTime( maxIdleTime ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getMaxPoolSize()
+    { return wcpds.getMaxPoolSize(); }
+
+    public void setMaxPoolSize( int maxPoolSize )
+    { 
+        wcpds.setMaxPoolSize( maxPoolSize ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getMaxStatements()
+    { return wcpds.getMaxStatements(); }
+
+    public void setMaxStatements( int maxStatements )
+    { 
+        wcpds.setMaxStatements( maxStatements ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getMaxStatementsPerConnection()
+    { return wcpds.getMaxStatementsPerConnection(); }
+
+    public void setMaxStatementsPerConnection( int maxStatementsPerConnection )
+    { 
+        wcpds.setMaxStatementsPerConnection( maxStatementsPerConnection ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getMinPoolSize()
+    { return wcpds.getMinPoolSize(); }
+
+    public void setMinPoolSize( int minPoolSize )
+    { 
+        wcpds.setMinPoolSize( minPoolSize ); 
+        this.resetPoolManager( false );
+    }
+
+    public String getOverrideDefaultUser()
+    { return wcpds.getOverrideDefaultUser(); }
+
+    public void setOverrideDefaultUser(String overrideDefaultUser)
+    { 
+        wcpds.setOverrideDefaultUser( overrideDefaultUser ); 
+        this.resetPoolManager( false );
+    }
+
+    public String getOverrideDefaultPassword()
+    { return wcpds.getOverrideDefaultPassword(); }
+
+    public void setOverrideDefaultPassword(String overrideDefaultPassword)
+    { 
+        wcpds.setOverrideDefaultPassword( overrideDefaultPassword ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getPropertyCycle()
+    { return wcpds.getPropertyCycle(); }
+
+    public void setPropertyCycle( int propertyCycle )
+    { 
+        wcpds.setPropertyCycle( propertyCycle ); 
+        this.resetPoolManager( false );
+    }
+
+    public boolean isBreakAfterAcquireFailure()
+    { return wcpds.isBreakAfterAcquireFailure(); }
+
+    public void setBreakAfterAcquireFailure( boolean breakAfterAcquireFailure )
+    { 
+        wcpds.setBreakAfterAcquireFailure( breakAfterAcquireFailure ); 
+        this.resetPoolManager( false );
+    }
+
+    public boolean isTestConnectionOnCheckout()
+    { return wcpds.isTestConnectionOnCheckout(); }
+
+    public void setTestConnectionOnCheckout( boolean testConnectionOnCheckout )
+    { 
+        wcpds.setTestConnectionOnCheckout( testConnectionOnCheckout ); 
+        this.resetPoolManager( false );
+    }
+
+    public boolean isTestConnectionOnCheckin()
+    { return wcpds.isTestConnectionOnCheckin(); }
+
+    public void setTestConnectionOnCheckin( boolean testConnectionOnCheckin )
+    { 
+        wcpds.setTestConnectionOnCheckin( testConnectionOnCheckin ); 
+        this.resetPoolManager( false );
+    }
+
+    public boolean isUsesTraditionalReflectiveProxies()
+    { return wcpds.isUsesTraditionalReflectiveProxies(); }
+
+    public void setUsesTraditionalReflectiveProxies( boolean usesTraditionalReflectiveProxies )
+    { 
+        wcpds.setUsesTraditionalReflectiveProxies( usesTraditionalReflectiveProxies ); 
+        this.resetPoolManager( false );
+    }
+
+    public String getPreferredTestQuery()
+    { return wcpds.getPreferredTestQuery(); }
+
+    public void setPreferredTestQuery( String preferredTestQuery )
+    { 
+        wcpds.setPreferredTestQuery( preferredTestQuery ); 
+        this.resetPoolManager( false );
+    }
+
+    public String getUserOverridesAsString()
+    { return wcpds.getUserOverridesAsString(); }
+
+    public void setUserOverridesAsString( String userOverridesAsString ) throws PropertyVetoException
+    { 
+        wcpds.setUserOverridesAsString( userOverridesAsString ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getMaxAdministrativeTaskTime()
+    { return wcpds.getMaxAdministrativeTaskTime(); }
+
+    public void setMaxAdministrativeTaskTime( int maxAdministrativeTaskTime )
+    { 
+        wcpds.setMaxAdministrativeTaskTime( maxAdministrativeTaskTime ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getMaxIdleTimeExcessConnections()
+    { return wcpds.getMaxIdleTimeExcessConnections(); }
+
+    public void setMaxIdleTimeExcessConnections( int maxIdleTimeExcessConnections )
+    { 
+        wcpds.setMaxIdleTimeExcessConnections( maxIdleTimeExcessConnections ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getMaxConnectionAge()
+    { return wcpds.getMaxConnectionAge(); }
+
+    public void setMaxConnectionAge( int maxConnectionAge )
+    { 
+        wcpds.setMaxConnectionAge( maxConnectionAge ); 
+        this.resetPoolManager( false );
+    }
+
+    public String getConnectionCustomizerClassName()
+    { return wcpds.getConnectionCustomizerClassName(); }
+
+    public void setConnectionCustomizerClassName( String connectionCustomizerClassName )
+    { 
+        wcpds.setConnectionCustomizerClassName( connectionCustomizerClassName ); 
+        this.resetPoolManager( false );
+    }
+
+    public int getUnreturnedConnectionTimeout()
+    { return wcpds.getUnreturnedConnectionTimeout(); }
+
+    public void setUnreturnedConnectionTimeout(int unreturnedConnectionTimeout)
+    {
+        wcpds.setUnreturnedConnectionTimeout( unreturnedConnectionTimeout ); 
+        this.resetPoolManager( false );
+    }
+
+    public boolean isDebugUnreturnedConnectionStackTraces()
+    { return wcpds.isDebugUnreturnedConnectionStackTraces(); }
+
+    public void setDebugUnreturnedConnectionStackTraces(boolean debugUnreturnedConnectionStackTraces)
+    {
+        wcpds.setDebugUnreturnedConnectionStackTraces( debugUnreturnedConnectionStackTraces ); 
+        this.resetPoolManager( false );
+    }
+
+    // shared properties (count: 1)
+    public String getFactoryClassLocation()
+    { return super.getFactoryClassLocation(); }
+
+    public void setFactoryClassLocation( String factoryClassLocation )
+    {
+        dmds.setFactoryClassLocation( factoryClassLocation );
+        wcpds.setFactoryClassLocation( factoryClassLocation );
+        super.setFactoryClassLocation( factoryClassLocation );
+    }
+
+    public String toString()
+    {
+        //System.err.println("ComboPooledDataSource.toString()");
+
+        StringBuffer sb = new StringBuffer(512);
+        sb.append( this.getClass().getName() );
+        sb.append(" [ ");
+        try { BeansUtils.appendPropNamesAndValues(sb, this, TO_STRING_IGNORE_PROPS); }
+        catch (Exception e)
+        { 
+            sb.append( e.toString() ); 
+            //e.printStackTrace();
+        }
+        sb.append(" ]");
+
+//      Map userOverrides = wcpds.getUserOverrides();
+//      if (userOverrides != null)
+//      sb.append("; userOverrides: " + userOverrides.toString());
+
+        return sb.toString();
+    }
+
+    // serialization stuff -- set up bound/constrained property event handlers on deserialization
+    private static final long serialVersionUID = 1;
+    private static final short VERSION = 0x0001;
+
+    private void writeObject( ObjectOutputStream oos ) throws IOException
+    {
+        oos.writeShort( VERSION );
+    }
+
+    private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
+    {
+        short version = ois.readShort();
+        switch (version)
+        {
+        case VERSION:
+            updateLocalVarsFromCpdsProp();
+            setUpPropertyEvents();
+            break;
+        default:
+            throw new IOException("Unsupported Serialized Version: " + version);
+        }
+    }
+}
+
+//now, referenceability happens exactly the same way it does for PoolBackedDataSource
+//all this stuff (and the maintenance hassle of it) should be unnecessary
+
+/*
+ // WrapperConnectionPoolDataSource properties -- count: 28
+//
+// 	("checkoutTimeout");
+// 	("acquireIncrement");
+// 	("acquireRetryAttempts");
+// 	("acquireRetryDelay");
+// 	("autoCommitOnClose");
+// 	("connectionTesterClassName");
+// 	("forceIgnoreUnresolvedTransactions");
+// 	("idleConnectionTestPeriod");
+// 	("initialPoolSize");
+// 	("maxIdleTime");
+// 	("maxPoolSize");
+// 	("maxStatements");
+// 	("maxStatementsPerConnection");
+// 	("minPoolSize");
+// 	("propertyCycle");
+// 	("breakAfterAcquireFailure");
+// 	("testConnectionOnCheckout");
+// 	("testConnectionOnCheckin");
+// 	("usesTraditionalReflectiveProxies");
+// 	("preferredTestQuery");
+// 	("automaticTestTable");
+// 	("userOverridesAsString");
+// 	("overrideDefaultUser");
+// 	("overrideDefaultPassword");
+// 	("maxAdministrativeTaskTime");
+// 	("maxIdleTimeExcessConnections");
+// 	("maxConnectionAge");
+// 	("connectionTesterClassName");
+
+   final static JavaBeanReferenceMaker referenceMaker = new JavaBeanReferenceMaker();
+
+    static
+    {
+	referenceMaker.setFactoryClassName( C3P0JavaBeanObjectFactory.class.getName() );
+
+	// DriverManagerDataSource properties (count: 4)
+	referenceMaker.addReferenceProperty("description");
+	referenceMaker.addReferenceProperty("driverClass");
+	referenceMaker.addReferenceProperty("jdbcUrl");
+	referenceMaker.addReferenceProperty("properties");
+
+	// WrapperConnectionPoolDataSource properties (count: 27)
+	referenceMaker.addReferenceProperty("checkoutTimeout");
+	referenceMaker.addReferenceProperty("acquireIncrement");
+	referenceMaker.addReferenceProperty("acquireRetryAttempts");
+	referenceMaker.addReferenceProperty("acquireRetryDelay");
+	referenceMaker.addReferenceProperty("autoCommitOnClose");
+	referenceMaker.addReferenceProperty("connectionTesterClassName");
+	referenceMaker.addReferenceProperty("forceIgnoreUnresolvedTransactions");
+	referenceMaker.addReferenceProperty("idleConnectionTestPeriod");
+	referenceMaker.addReferenceProperty("initialPoolSize");
+	referenceMaker.addReferenceProperty("maxIdleTime");
+	referenceMaker.addReferenceProperty("maxPoolSize");
+	referenceMaker.addReferenceProperty("maxStatements");
+	referenceMaker.addReferenceProperty("maxStatementsPerConnection");
+	referenceMaker.addReferenceProperty("minPoolSize");
+	referenceMaker.addReferenceProperty("propertyCycle");
+	referenceMaker.addReferenceProperty("breakAfterAcquireFailure");
+	referenceMaker.addReferenceProperty("testConnectionOnCheckout");
+	referenceMaker.addReferenceProperty("testConnectionOnCheckin");
+	referenceMaker.addReferenceProperty("usesTraditionalReflectiveProxies");
+	referenceMaker.addReferenceProperty("preferredTestQuery");
+	referenceMaker.addReferenceProperty("automaticTestTable");
+	referenceMaker.addReferenceProperty("userOverridesAsString");
+	referenceMaker.addReferenceProperty("overrideDefaultUser");
+	referenceMaker.addReferenceProperty("overrideDefaultPassword");
+	referenceMaker.addReferenceProperty("maxAdministrativeTaskTime");
+	referenceMaker.addReferenceProperty("maxIdleTimeExcessConnections");
+	referenceMaker.addReferenceProperty("maxConnectionAge");
+
+	// PoolBackedDataSource properties (count: 2)
+	referenceMaker.addReferenceProperty("dataSourceName");
+	referenceMaker.addReferenceProperty("numHelperThreads");
+
+	// identity token
+	referenceMaker.addReferenceProperty("identityToken");
+
+	// shared properties (count: 1)
+	referenceMaker.addReferenceProperty("factoryClassLocation");
+    }
+
+    public Reference getReference() throws NamingException
+    { 
+	synchronized ( this )
+	    {
+		synchronized ( wcpds )
+		    {
+			synchronized( dmds )
+			    {
+				//System.err.println("ComboPooledDataSource.getReference()!!!!");
+				//new Exception("PRINT-STACK-TRACE").printStackTrace();
+				//javax.naming.Reference out = referenceMaker.createReference( this ); 
+				//System.err.println(out);
+				//return out;
+
+				return referenceMaker.createReference( this ); 
+			    }
+		    }
+	    }
+    }
+ */
diff --git a/src/classes/com/mchange/v2/c3p0/ConnectionCustomizer.java b/src/classes/com/mchange/v2/c3p0/ConnectionCustomizer.java
new file mode 100644
index 0000000..e4b0b0a
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/ConnectionCustomizer.java
@@ -0,0 +1,89 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ *  <p>Implementations of this interface should
+ *  be immutable, and should offer public,
+ *  no argument constructors.</p>
+ *
+ *  <p>The methods are handed raw, physical
+ *  database Connections, not c3p0-generated
+ *  proxies.</p>
+ *
+ *  <p>Although c3p0 will ensure this with
+ *  respect to state controlled by
+ *  standard JDBC methods, any modifications
+ *  of vendor-specific state shold be made
+ *  consistently so that all Connections
+ *  in the pool are interchangable.</p>
+ */
+public interface ConnectionCustomizer
+{
+    /**
+     *  <p>Called immediately after a 
+     *  Connection is acquired from the
+     *  underlying database for 
+     *  incorporation into the pool.</p>
+     *
+     *  <p>This method is only called once
+     *  per Connection. If standard JDBC
+     *  Connection properties are modified
+     *  [holdability, transactionIsolation,
+     *  readOnly], those modifications
+     *  will override defaults throughout
+     *  the Connection's tenure in the
+     *  pool.</p>
+     */
+    public void onAcquire( Connection c, String parentDataSourceIdentityToken )
+	throws Exception;
+
+    /**
+     *  Called immediately before a 
+     *  Connection is destroyed after
+     *  being removed from the pool.
+     */
+    public void onDestroy( Connection c, String parentDataSourceIdentityToken )
+	throws Exception;
+
+    /**
+     *  Called immediately before a 
+     *  Connection is made available to
+     *  a client upon checkout.
+     */
+    public void onCheckOut( Connection c, String parentDataSourceIdentityToken )
+	throws Exception;
+
+    /**
+     *  Called immediately after a 
+     *  Connection is checked in,
+     *  prior to reincorporation
+     *  into the pool.
+     */
+    public void onCheckIn( Connection c, String parentDataSourceIdentityToken )
+	throws Exception;
+}
diff --git a/src/classes/com/mchange/v2/c3p0/ConnectionTester.java b/src/classes/com/mchange/v2/c3p0/ConnectionTester.java
new file mode 100644
index 0000000..2646db5
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/ConnectionTester.java
@@ -0,0 +1,65 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.io.Serializable;
+import java.sql.Connection;
+
+/**
+ *  <p>Define your own Connection tester if you want to
+ *  override c3p0's default behavior for testing the validity
+ *  of Connections and responding to Connection errors encountered.</p>
+ *
+ *  <p><b>Recommended:</b> If you'd like your ConnectionTester
+ *  to support the user-configured <tt>preferredTestQuery</tt>
+ *  parameter, please implement {@link com.mchange.v2.c3p0.UnifiedConnectionTester}.
+ *
+ *  <p>ConnectionTesters should be Serializable, immutable, 
+ *  and must have public, no-arg constructors.</p>
+ *  
+ *  @see com.mchange.v2.c3p0.UnifiedConnectionTester
+ *  @see com.mchange.v2.c3p0.AbstractConnectionTester
+ */
+public interface ConnectionTester extends Serializable
+{
+    public final static int CONNECTION_IS_OKAY       =  0;
+    public final static int CONNECTION_IS_INVALID    = -1;
+    public final static int DATABASE_IS_INVALID      = -8;
+
+    public int activeCheckConnection(Connection c);
+
+    public int statusOnException(Connection c, Throwable t);
+
+    /**
+     * Multiple testers that are of the same
+     * class and use the same criteria for determining fatality
+     * should test as equals().
+     */
+    public boolean equals( Object o );
+
+    /**
+     * keep consistent with equals()
+     */
+    public int hashCode();
+}
diff --git a/src/classes/com/mchange/v2/c3p0/DataSources.java b/src/classes/com/mchange/v2/c3p0/DataSources.java
new file mode 100644
index 0000000..3e3f992
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/DataSources.java
@@ -0,0 +1,368 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import com.mchange.v2.log.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyVetoException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+import javax.sql.ConnectionPoolDataSource;
+import com.mchange.v2.sql.SqlUtils;
+import com.mchange.v2.beans.BeansUtils;
+
+/**
+ *  <p>A simple factory class for creating DataSources. Generally, users will call <tt>DataSources.unpooledDataSource()</tt> to get
+ *  a basic DataSource, and then get a pooled version by calling <tt>DataSources.pooledDataSource()</tt>.</p>
+ *
+ *  <p>Most users will not need to worry about configuration details. If you want to use a PreparedStatement cache, be sure to call
+ *  the version of <tt>DataSources.pooledDataSource()</tt> that accepts a <tt>statement_cache_size</tt> parameter, and set that to
+ *  be a number (much) greater than zero. (For maximum performance, you would set this to be several times the number kinds of 
+ *  PreparedStatements you expect your application to use.)</p>
+ *
+ * <p>For those interested in detailed configuration, note that c3p0 pools can be configured by explicit method calls on PoolConfig objects,
+ * by defining System properties, or by defining a <tt>c3p0.properties</tt> file in your resource path. See {@link com.mchange.v2.c3p0.PoolConfig}
+ * for details.</p>
+ *
+ */
+public final class DataSources
+{
+    final static MLogger logger = MLog.getLogger( DataSources.class );
+
+    final static Set WRAPPER_CXN_POOL_DATA_SOURCE_OVERWRITE_PROPS; //22 -- includes factory class location
+    final static Set POOL_BACKED_DATA_SOURCE_OVERWRITE_PROPS; //2 -- includes factory class location, excludes pool-owner id token
+
+    static
+    {
+	// As of c3p0-0.9.1
+	//
+	// This list is no longer updated, as the PoolConfig approach to setting up DataSources
+	// is now deprecated. (This was getting to be hard to maintain as new config properties
+	// were added.)
+	String[] props = new String[]
+	{
+	    "checkoutTimeout", //1 
+	    "acquireIncrement",  //2
+	    "acquireRetryAttempts", //3
+	    "acquireRetryDelay", //4
+	    "autoCommitOnClose", //5
+	    "connectionTesterClassName", //6
+	    "forceIgnoreUnresolvedTransactions", //7
+	    "idleConnectionTestPeriod", //8
+	    "initialPoolSize", //9
+	    "maxIdleTime", //10
+	    "maxPoolSize", //11
+	    "maxStatements", //12
+	    "maxStatementsPerConnection", //13
+	    "minPoolSize", //14
+	    "propertyCycle", //15
+	    "breakAfterAcquireFailure", //16
+	    "testConnectionOnCheckout", //17
+	    "testConnectionOnCheckin", //18
+	    "usesTraditionalReflectiveProxies", //19
+	    "preferredTestQuery", //20
+	    "automaticTestTable", //21
+	    "factoryClassLocation" //22
+	};
+
+	WRAPPER_CXN_POOL_DATA_SOURCE_OVERWRITE_PROPS = Collections.unmodifiableSet( new HashSet( Arrays.asList( props ) ) );
+
+	// As of c3p0-0.9.1
+	//
+	// This list is no longer updated, as the PoolConfig approach to setting up DataSources
+	// is now deprecated. (This was getting to be hard to maintain as new config properties
+	// were added.)
+	props = new String[]
+	{
+	    "numHelperThreads",
+	    "factoryClassLocation"
+	};
+
+	POOL_BACKED_DATA_SOURCE_OVERWRITE_PROPS = Collections.unmodifiableSet( new HashSet( Arrays.asList( props ) ) );
+    }
+
+    /**
+     * Defines an unpooled DataSource all of whose paramateres (especially jdbcUrl)
+     * should be set in config files.
+     */
+    public static DataSource unpooledDataSource() throws SQLException
+    { 
+	DriverManagerDataSource out = new DriverManagerDataSource();
+	return out;
+    }
+
+    public static DataSource unpooledDataSource(String jdbcUrl) throws SQLException
+    { 
+    DriverManagerDataSource out = new DriverManagerDataSource();
+    out.setJdbcUrl( jdbcUrl );
+    return out;
+    }
+
+    /**
+     * Defines an unpooled DataSource on the specified JDBC URL, authenticating with a username and password.
+     */
+    public static DataSource unpooledDataSource(String jdbcUrl, String user, String password) throws SQLException
+    {
+	Properties props = new Properties();
+	props.put(SqlUtils.DRIVER_MANAGER_USER_PROPERTY, user);
+	props.put(SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY, password);
+	return unpooledDataSource( jdbcUrl, props ); 
+    }
+
+    /**
+     * Defines an unpooled DataSource on the specified JDBC URL.
+     *
+     *  @param driverProps the usual DriverManager properties for your JDBC driver
+     *         (e.g. "user" and "password" for all drivers that support
+     *         authentication)
+     * 
+     *  @see java.sql.DriverManager
+     */
+    public static DataSource unpooledDataSource(String jdbcUrl, Properties driverProps) throws SQLException
+    {
+	DriverManagerDataSource out = new DriverManagerDataSource();
+	out.setJdbcUrl( jdbcUrl );
+	out.setProperties( driverProps );
+	return out;
+    }
+
+    /**
+     * <p>Creates a pooled version of an unpooled DataSource using default configuration information.</p>
+     * <p><b>NOTE:</b> By default, statement pooling is turned off, because for simple databases that do
+     *                 not pre-parse and optimize PreparedStatements, statement caching is a net
+     *                 performance loss. But if your database <i>does</i> optimize PreparedStatements
+     *                 you'll want to turn StatementCaching on via {@link #pooledDataSource(javax.sql.DataSource, int)}.</p>
+     *  @return a DataSource that can be cast to a {@link PooledDataSource} if you are interested in pool statistics
+     */
+    public static DataSource pooledDataSource( DataSource unpooledDataSource ) throws SQLException
+    { return pooledDataSource( unpooledDataSource, null, (Map) null ); }
+
+    /**
+     * <p>Creates a pooled version of an unpooled DataSource using default configuration information 
+     *    and the specified startement cache size.
+     *    Use a value greater than zero to turn statement caching on.</p>
+     *
+     *  @return a DataSource that can be cast to a {@link PooledDataSource} if you are interested in pool statistics
+     */
+    public static DataSource pooledDataSource( DataSource unpooledDataSource, int statement_cache_size ) throws SQLException
+    {
+// 	PoolConfig pcfg = new PoolConfig();
+// 	pcfg.setMaxStatements( statement_cache_size );
+
+	Map overrideProps = new HashMap();
+	overrideProps.put( "maxStatements", new Integer( statement_cache_size ) );
+	return pooledDataSource( unpooledDataSource, null, overrideProps );
+    }
+
+    /**
+     * <p>Creates a pooled version of an unpooled DataSource using configuration 
+     *    information supplied explicitly by a {@link com.mchange.v2.c3p0.PoolConfig}.
+     *
+     *  @return a DataSource that can be cast to a {@link PooledDataSource} if you are interested in pool statistics
+     *
+     *  @deprecated if you want to set properties programmatically, please construct a ComboPooledDataSource and
+     *              set its properties rather than using PoolConfig
+     */
+    public static DataSource pooledDataSource( DataSource unpooledDataSource, PoolConfig pcfg ) throws SQLException
+    {
+	try
+	    {
+		WrapperConnectionPoolDataSource wcpds = new WrapperConnectionPoolDataSource();
+		wcpds.setNestedDataSource( unpooledDataSource );
+		
+		// set PoolConfig info -- WrapperConnectionPoolDataSource properties 
+		BeansUtils.overwriteSpecificAccessibleProperties( pcfg, wcpds, WRAPPER_CXN_POOL_DATA_SOURCE_OVERWRITE_PROPS );
+		
+		PoolBackedDataSource nascent_pbds = new PoolBackedDataSource();
+		nascent_pbds.setConnectionPoolDataSource( wcpds );
+		BeansUtils.overwriteSpecificAccessibleProperties( pcfg, nascent_pbds, POOL_BACKED_DATA_SOURCE_OVERWRITE_PROPS );
+
+		return nascent_pbds;
+	    }
+// 	catch ( PropertyVetoException e )
+// 	    {
+// 		e.printStackTrace();
+// 		PropertyChangeEvent evt = e.getPropertyChangeEvent();
+// 		throw new SQLException("Illegal value attempted for property " + evt.getPropertyName() + ": " + evt.getNewValue());
+// 	    }
+ 	catch ( Exception e )
+ 	    {
+ 		//e.printStackTrace();
+		SQLException sqle = SqlUtils.toSQLException("Exception configuring pool-backed DataSource: " + e, e);
+		if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED && logger.isLoggable( MLevel.FINE ) && e != sqle)
+		    logger.log( MLevel.FINE, "Converted exception to throwable SQLException", e );
+ 		throw sqle;
+ 	    }
+    }
+
+    /*
+    public static DataSource pooledDataSource( DataSource unpooledDataSource, String overrideDefaultUser, String overrideDefaultPassword ) throws SQLException
+    {
+	Map overrideProps;
+
+	if (overrideDefaultUser != null)
+	    {
+		overrideProps = new HashMap();
+		overrideProps.put( "overrideDefaultUser", overrideDefaultUser );
+		overrideProps.put( "overrideDefaultPassword", overrideDefaultPassword );
+	    }
+	else
+	    overrideProps = null;
+
+	return pooledDataSource( unpooledDataSource, null, overrideProps );
+    }
+    */
+
+    public static DataSource pooledDataSource( DataSource unpooledDataSource, String configName ) throws SQLException
+    { return pooledDataSource( unpooledDataSource, configName, null ); }
+
+    public static DataSource pooledDataSource( DataSource unpooledDataSource, Map overrideProps ) throws SQLException
+    { return pooledDataSource( unpooledDataSource, null, overrideProps ); }
+
+    public static DataSource pooledDataSource( DataSource unpooledDataSource, String configName, Map overrideProps ) throws SQLException
+    {
+	try
+	    {
+		WrapperConnectionPoolDataSource wcpds = new WrapperConnectionPoolDataSource(configName);
+		wcpds.setNestedDataSource( unpooledDataSource );
+		if (overrideProps != null)
+		    BeansUtils.overwriteAccessiblePropertiesFromMap( overrideProps, 
+								     wcpds, 
+								     false,
+								     null,
+								     true,
+								     MLevel.WARNING,
+								     MLevel.WARNING,
+								     false);
+		
+		PoolBackedDataSource nascent_pbds = new PoolBackedDataSource(configName);
+		nascent_pbds.setConnectionPoolDataSource( wcpds );
+		if (overrideProps != null)
+		    BeansUtils.overwriteAccessiblePropertiesFromMap( overrideProps, 
+								     nascent_pbds, 
+								     false,
+								     null,
+								     true,
+								     MLevel.WARNING,
+								     MLevel.WARNING,
+								     false);
+
+		return nascent_pbds;
+	    }
+// 	catch ( PropertyVetoException e )
+// 	    {
+// 		e.printStackTrace();
+// 		PropertyChangeEvent evt = e.getPropertyChangeEvent();
+// 		throw new SQLException("Illegal value attempted for property " + evt.getPropertyName() + ": " + evt.getNewValue());
+// 	    }
+ 	catch ( Exception e )
+ 	    {
+ 		//e.printStackTrace();
+		SQLException sqle = SqlUtils.toSQLException("Exception configuring pool-backed DataSource: " + e, e);
+		if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED && logger.isLoggable( MLevel.FINE ) && e != sqle)
+		    logger.log( MLevel.FINE, "Converted exception to throwable SQLException", e );
+ 		throw sqle;
+ 	    }
+    }
+
+    /**
+     * <p>Creates a pooled version of an unpooled DataSource using configuration 
+     *    information supplied explicitly by a Java Properties object.</p>
+     *
+     *  @return a DataSource that can be cast to a {@link PooledDataSource} if you are interested in pool statistics
+     *  @see com.mchange.v2.c3p0.PoolConfig
+     */
+    public static DataSource pooledDataSource( DataSource unpooledDataSource, Properties props ) throws SQLException
+    { 
+	//return pooledDataSource( unpooledDataSource, new PoolConfig( props ) ); 
+
+	Properties peeledProps = new Properties();
+	for (Iterator ii = props.keySet().iterator(); ii.hasNext(); )
+	    {
+		String propKey = (String) ii.next();
+		String propVal = props.getProperty( propKey );
+		String peeledKey = (propKey.startsWith("c3p0.") ? propKey.substring(5) : propKey );
+		peeledProps.put( peeledKey, propVal );
+	    }
+	return pooledDataSource( unpooledDataSource, null, peeledProps );
+    }
+
+    /**
+     * <p>Immediately releases resources (Threads and database Connections) that are
+     *    held by a C3P0 DataSource.
+     *
+     * <p>Only DataSources created by the poolingDataSource() method hold any
+     *    non-memory resources. Calling this method on unpooled DataSources is
+     *    effectively a no-op.</p>
+     *
+     * <p>You can safely presume that destroying a pooled DataSource that is wrapped around
+     *    another DataSource created by this library destroys both the outer and the wrapped
+     *    DataSource. There is no reason to hold a reference to a nested DataSource in order
+     *    to explicitly destroy it.</p>
+     *
+     *  @see com.mchange.v2.c3p0.PoolConfig
+     */
+    public static void destroy( DataSource pooledDataSource ) throws SQLException
+    { destroy( pooledDataSource, false ); }
+
+
+    /**
+     *   @deprecated forceDestroy() is no longer meaningful, as a set of pools is now
+     *               directly associated with a DataSource, and not potentially shared.
+     *               (This simplification was made possible by canonicalization of 
+     *               JNDI-looked-up DataSources within a virtual machine.) Just use
+     *               DataSources.destroy().
+     *
+     *   @see #destroy
+     */
+    public static void forceDestroy( DataSource pooledDataSource ) throws SQLException
+    { destroy( pooledDataSource, true ); }
+
+    private static void destroy( DataSource pooledDataSource, boolean force ) throws SQLException
+    {
+	if ( pooledDataSource instanceof PoolBackedDataSource)
+	    {
+		ConnectionPoolDataSource cpds = ((PoolBackedDataSource) pooledDataSource).getConnectionPoolDataSource();
+		if (cpds instanceof WrapperConnectionPoolDataSource)
+		    destroy( ((WrapperConnectionPoolDataSource) cpds).getNestedDataSource(), force );
+	    }
+	if ( pooledDataSource instanceof PooledDataSource )
+	    ((PooledDataSource) pooledDataSource).close( force );
+    }
+
+    private DataSources()
+    {}
+}
+
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/DriverManagerDataSource.java b/src/classes/com/mchange/v2/c3p0/DriverManagerDataSource.java
new file mode 100644
index 0000000..ba47c56
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/DriverManagerDataSource.java
@@ -0,0 +1,254 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.PrintWriter;
+import java.util.Properties;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+import com.mchange.v2.sql.SqlUtils;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import com.mchange.v2.c3p0.cfg.C3P0Config;
+import com.mchange.v2.c3p0.impl.DriverManagerDataSourceBase;
+
+public final class DriverManagerDataSource extends DriverManagerDataSourceBase implements DataSource
+{
+    final static MLogger logger = MLog.getLogger( DriverManagerDataSource.class );
+
+    //MT: protected by this' lock
+    Driver driver;
+    
+    //MT: protected by this' lock
+    boolean driver_class_loaded = false;
+
+    public DriverManagerDataSource()
+    { this( true ); }
+
+    public DriverManagerDataSource(boolean autoregister)
+    {
+        super( autoregister );
+
+        setUpPropertyListeners();
+
+        String user = C3P0Config.initializeStringPropertyVar("user", null);
+        String password = C3P0Config.initializeStringPropertyVar("password", null);
+
+        if (user != null)
+            this.setUser( user );
+
+        if (password != null)
+            this.setPassword( password );
+    }
+
+    private void setUpPropertyListeners()
+    {
+        PropertyChangeListener driverClassListener = new PropertyChangeListener()
+        {
+            public void propertyChange( PropertyChangeEvent evt )
+            {
+                if ( "driverClass".equals( evt.getPropertyName() ) )
+                    setDriverClassLoaded( false );
+            }
+        };
+        this.addPropertyChangeListener( driverClassListener );
+    }
+    
+    private synchronized boolean isDriverClassLoaded()
+    { return driver_class_loaded; }
+    
+    private synchronized void setDriverClassLoaded(boolean dcl)
+    { this.driver_class_loaded = dcl; }
+    
+    private void ensureDriverLoaded() throws SQLException
+    {
+        try
+        {
+            if (! isDriverClassLoaded())
+            {
+                if (driverClass != null)
+                    Class.forName( driverClass );
+                setDriverClassLoaded( true );
+            }
+        }
+        catch (ClassNotFoundException e)
+        {
+            if (logger.isLoggable(MLevel.WARNING))
+                logger.log(MLevel.WARNING, "Could not load driverClass " + driverClass, e);
+        }
+    }
+
+    // should NOT be sync'ed -- driver() is sync'ed and that's enough
+    // sync'ing the method creates the danger that one freeze on connect
+    // blocks access to the entire DataSource
+
+    public Connection getConnection() throws SQLException
+    { 
+        ensureDriverLoaded();
+
+        Connection out = driver().connect( jdbcUrl, properties ); 
+        if (out == null)
+            throw new SQLException("Apparently, jdbc URL '" + jdbcUrl + "' is not valid for the underlying " +
+                            "driver [" + driver() + "].");
+        return out;
+    }
+
+    // should NOT be sync'ed -- driver() is sync'ed and that's enough
+    // sync'ing the method creates the danger that one freeze on connect
+    // blocks access to the entire DataSource
+
+    public Connection getConnection(String username, String password) throws SQLException
+    { 
+        ensureDriverLoaded();
+
+        Connection out = driver().connect( jdbcUrl, overrideProps(username, password) );  
+        if (out == null)
+            throw new SQLException("Apparently, jdbc URL '" + jdbcUrl + "' is not valid for the underlying " +
+                            "driver [" + driver() + "].");
+        return out;
+    }
+
+    public PrintWriter getLogWriter() throws SQLException
+    { return DriverManager.getLogWriter(); }
+
+    public void setLogWriter(PrintWriter out) throws SQLException
+    { DriverManager.setLogWriter( out ); }
+
+    public int getLoginTimeout() throws SQLException
+    { return DriverManager.getLoginTimeout(); }
+
+    public void setLoginTimeout(int seconds) throws SQLException
+    { DriverManager.setLoginTimeout( seconds ); }
+
+    //overrides
+    public synchronized void setJdbcUrl(String jdbcUrl)
+    {
+        //System.err.println( "setJdbcUrl( " + jdbcUrl + " )");
+        //new Exception("DEBUG STACK TRACE").printStackTrace();
+        super.setJdbcUrl( jdbcUrl );
+        clearDriver();
+    }
+
+    //"virtual properties"
+    public synchronized void setUser(String user)
+    {
+        String oldUser = this.getUser();
+        if (! eqOrBothNull( user, oldUser ))
+        {
+            if (user != null)
+                properties.put( SqlUtils.DRIVER_MANAGER_USER_PROPERTY, user ); 
+            else
+                properties.remove( SqlUtils.DRIVER_MANAGER_USER_PROPERTY );
+
+            pcs.firePropertyChange("user", oldUser, user);
+        }
+    }
+
+    public synchronized String getUser()
+    {
+//      System.err.println("getUser() -- DriverManagerDataSource@" + System.identityHashCode( this ) + 
+//      " using Properties@" + System.identityHashCode( properties ));
+//      new Exception("STACK TRACE DUMP").printStackTrace();
+        return properties.getProperty( SqlUtils.DRIVER_MANAGER_USER_PROPERTY ); 
+    }
+
+    public synchronized void setPassword(String password)
+    {
+        String oldPass = this.getPassword();
+        if (! eqOrBothNull( password, oldPass ))
+        {
+            if (password != null)
+                properties.put( SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY, password ); 
+            else
+                properties.remove( SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY );
+
+            pcs.firePropertyChange("password", oldPass, password);
+        }
+    }
+
+    public synchronized String getPassword()
+    { return properties.getProperty( SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY ); }
+
+    private final Properties overrideProps(String user, String password)
+    {
+        Properties overriding = (Properties) properties.clone(); //we are relying on a defensive clone in our base class!!!
+
+        if (user != null)
+            overriding.put(SqlUtils.DRIVER_MANAGER_USER_PROPERTY, user);
+        else
+            overriding.remove(SqlUtils.DRIVER_MANAGER_USER_PROPERTY);
+
+        if (password != null)
+            overriding.put(SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY, password);
+        else
+            overriding.remove(SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY);
+
+        return overriding;
+    }
+
+    private synchronized Driver driver() throws SQLException
+    {
+        //System.err.println( "driver() <-- " + this );
+        if (driver == null)
+            driver = DriverManager.getDriver( jdbcUrl );
+        return driver;
+    }
+
+    private synchronized void clearDriver()
+    { driver = null; }
+
+    private static boolean eqOrBothNull( Object a, Object b )
+    { return (a == b || (a != null && a.equals(b))); }
+
+    // serialization stuff -- set up bound/constrained property event handlers on deserialization
+    private static final long serialVersionUID = 1;
+    private static final short VERSION = 0x0001;
+
+    private void writeObject( ObjectOutputStream oos ) throws IOException
+    {
+        oos.writeShort( VERSION );
+    }
+
+    private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
+    {
+        short version = ois.readShort();
+        switch (version)
+        {
+        case VERSION:
+            setUpPropertyListeners();
+            break;
+        default:
+            throw new IOException("Unsupported Serialized Version: " + version);
+        }
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/DriverManagerDataSourceFactory.java b/src/classes/com/mchange/v2/c3p0/DriverManagerDataSourceFactory.java
new file mode 100644
index 0000000..08a97ec
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/DriverManagerDataSourceFactory.java
@@ -0,0 +1,154 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.beans.PropertyChangeEvent;
+import java.sql.SQLException;
+import java.util.Properties;
+import javax.sql.DataSource;
+
+/**
+ *  <P>A static factory that creates DataSources which simply forward
+ *     calls to java.sql.DriverManager without any pooling or other fanciness.</P>
+ *
+ *  <P>The DataSources returned are Refereneable and Serializable; they should
+ *     be suitable for placement in a wide variety of JNDI Naming Services.</P>
+ *
+ *  @deprecated Use the new factories in {@link com.mchange.v2.c3p0.DataSources}. See examples.
+ */
+public final class DriverManagerDataSourceFactory
+{
+    /**
+     *  Creates an unpooled DataSource that users <TT>java.sql.DriverManager</TT>
+     *  behind the scenes to acquire Connections.
+     *
+     *  @param driverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param dfltUser a username (may be null) for authentication to the RDBMS
+     *  @param dfltPassword a password (may be null) for authentication to the RDBMS
+     *  @param refFactoryLoc a codebase url where JNDI clients can find the  
+     *         c3p0 libraries. Use null if clients will be expected to have the
+     *         libraries available locally.
+     */
+    public static DataSource create(String driverClass,
+				    String jdbcUrl, 
+				    String dfltUser, 
+				    String dfltPassword, 
+				    String refFactoryLoc)
+	throws SQLException
+    { 
+		DriverManagerDataSource out = new DriverManagerDataSource();
+		out.setDriverClass( driverClass );
+		out.setJdbcUrl( jdbcUrl );
+		out.setUser( dfltUser );
+		out.setPassword( dfltPassword );
+		out.setFactoryClassLocation( refFactoryLoc );
+		return out;
+    }
+
+    /**
+     *  Creates an unpooled DataSource that users <TT>java.sql.DriverManager</TT>
+     *  behind the scenes to acquire Connections.
+     *
+     *  @param driverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param props propertis object that should be passed to DriverManager.getConnection()
+     *  @param refFactoryLoc a codebase url where JNDI clients can find the  
+     *         c3p0 libraries. Use null if clients will be expected to have the
+     *         libraries available locally.
+     */
+    public static DataSource create(String driverClass,
+				    String jdbcUrl, 
+				    Properties props,
+				    String refFactoryLoc)
+	throws SQLException
+    { 
+		DriverManagerDataSource out = new DriverManagerDataSource();
+		out.setDriverClass( driverClass );
+		out.setJdbcUrl( jdbcUrl );
+		out.setProperties( props );
+		out.setFactoryClassLocation( refFactoryLoc );
+		return out;
+    } 
+
+    /**
+     *  Creates an unpooled DataSource that users <TT>java.sql.DriverManager</TT>
+     *  behind the scenes to acquire Connections.
+     *
+     *  @param driverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param dfltUser a username (may be null) for authentication to the RDBMS
+     *  @param dfltPassword a password (may be null) for authentication to the RDBMS
+     */
+    public static DataSource create(String driverClass,
+				    String jdbcUrl, 
+				    String dfltUser, 
+				    String dfltPassword)
+	throws SQLException
+    { return create( driverClass, jdbcUrl, dfltUser, dfltPassword, null ); }
+
+    /**
+     *  Creates an unpooled DataSource that users <TT>java.sql.DriverManager</TT>
+     *  behind the scenes to acquire Connections.
+     *
+     *  @param driverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     */
+    public static DataSource create(String driverClass, String jdbcUrl)
+	throws SQLException
+    { return DriverManagerDataSourceFactory.create( driverClass, jdbcUrl, (String) null, null); }
+
+    /**
+     *  Creates an unpooled DataSource that users <TT>java.sql.DriverManager</TT>
+     *  behind the scenes to acquire Connections.
+     *
+     *  <P>Warning: since you do not set the driver class, the resulting DataSource
+     *  will be less suitable for use via JNDI: JNDI clients will have to
+     *  know the driver class and make sure themselves that it is preloaded!!!
+     *
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param dfltUser a username (may be null) for authentication to the RDBMS
+     *  @param dfltPassword a password (may be null) for authentication to the RDBMS
+     */
+    public static DataSource create(String jdbcUrl, String dfltUser, String dfltPassword)
+	throws SQLException
+    { return DriverManagerDataSourceFactory.create( null, jdbcUrl, dfltUser, dfltPassword ); }
+
+    /**
+     *  Creates an unpooled DataSource that users <TT>java.sql.DriverManager</TT>
+     *  behind the scenes to acquire Connections.
+     *
+     *  <P>Warning: since you do not set the driver class, the resulting DataSource
+     *  will be less suitable for use via JNDI: JNDI clients will have to
+     *  know the driver class and make sure themselves that it is preloaded!!!
+     *
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     */
+    public static DataSource create(String jdbcUrl)
+	throws SQLException
+    { return DriverManagerDataSourceFactory.create( null, jdbcUrl, (String) null, null ); }
+
+    private DriverManagerDataSourceFactory()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/c3p0/FullQueryConnectionTester.java b/src/classes/com/mchange/v2/c3p0/FullQueryConnectionTester.java
new file mode 100644
index 0000000..16eff05
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/FullQueryConnectionTester.java
@@ -0,0 +1,32 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.Connection;
+
+public interface FullQueryConnectionTester extends QueryConnectionTester
+{
+    public int statusOnException(Connection c, Throwable t, String preferredTestQuery);
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/JndiRefConnectionPoolDataSource.java b/src/classes/com/mchange/v2/c3p0/JndiRefConnectionPoolDataSource.java
new file mode 100644
index 0000000..c6bc462
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/JndiRefConnectionPoolDataSource.java
@@ -0,0 +1,311 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import com.mchange.v2.c3p0.impl.*;
+
+import java.beans.PropertyVetoException;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Hashtable;
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.PooledConnection;
+import com.mchange.v2.beans.BeansUtils;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import com.mchange.v2.naming.JavaBeanReferenceMaker;
+import com.mchange.v2.naming.JavaBeanObjectFactory;
+import com.mchange.v2.naming.ReferenceMaker;
+
+public final class JndiRefConnectionPoolDataSource extends IdentityTokenResolvable implements ConnectionPoolDataSource, Serializable, Referenceable
+{
+    final static MLogger logger = MLog.getLogger( JndiRefConnectionPoolDataSource.class );
+
+    final static Collection IGNORE_PROPS = Arrays.asList( new String[] {"reference", "pooledConnection"} );
+
+    JndiRefForwardingDataSource     jrfds;
+    WrapperConnectionPoolDataSource wcpds;
+
+    String identityToken;
+
+    public JndiRefConnectionPoolDataSource()
+    { this( true ); }
+
+    public JndiRefConnectionPoolDataSource( boolean autoregister )
+    {
+	jrfds = new JndiRefForwardingDataSource();
+	wcpds = new WrapperConnectionPoolDataSource();
+	wcpds.setNestedDataSource( jrfds );
+
+	if (autoregister)
+	    {
+		this.identityToken = C3P0ImplUtils.allocateIdentityToken( this );
+		C3P0Registry.reregister( this );
+	    }
+    }
+
+    public boolean isJndiLookupCaching()
+    { return jrfds.isCaching();  }
+    
+    public void setJndiLookupCaching( boolean caching )
+    { jrfds.setCaching( caching ); }
+    
+    public Hashtable getJndiEnv()
+    { return jrfds.getJndiEnv(); }
+    
+    public void setJndiEnv( Hashtable jndiEnv )
+    { jrfds.setJndiEnv( jndiEnv ); }
+    
+    public Object getJndiName()
+    { return jrfds.getJndiName(); }
+    
+    public void setJndiName( Object jndiName ) throws PropertyVetoException
+    { jrfds.setJndiName( jndiName ); }
+
+    public int getAcquireIncrement()
+    { return wcpds.getAcquireIncrement(); }
+	
+    public void setAcquireIncrement( int acquireIncrement )
+    { wcpds.setAcquireIncrement( acquireIncrement ); }
+	
+    public int getAcquireRetryAttempts()
+    { return wcpds.getAcquireRetryAttempts(); }
+	
+    public void setAcquireRetryAttempts( int ara )
+    { wcpds.setAcquireRetryAttempts( ara ); }
+	
+    public int getAcquireRetryDelay()
+    { return wcpds.getAcquireRetryDelay(); }
+	
+    public void setAcquireRetryDelay( int ard )
+    { wcpds.setAcquireRetryDelay( ard ); }
+	
+    public boolean isAutoCommitOnClose()
+    { return wcpds.isAutoCommitOnClose(); }
+
+    public void setAutoCommitOnClose( boolean autoCommitOnClose )
+    { wcpds.setAutoCommitOnClose( autoCommitOnClose ); }
+	
+    public void setAutomaticTestTable( String att )
+    { wcpds.setAutomaticTestTable( att ); }
+	
+    public String getAutomaticTestTable()
+    { return wcpds.getAutomaticTestTable(); }
+	
+    public void setBreakAfterAcquireFailure( boolean baaf )
+    { wcpds.setBreakAfterAcquireFailure( baaf ); }
+	
+    public boolean isBreakAfterAcquireFailure()
+    { return wcpds.isBreakAfterAcquireFailure(); }
+
+    public void setCheckoutTimeout( int ct )
+    { wcpds.setCheckoutTimeout( ct ); }
+
+    public int getCheckoutTimeout()
+    { return wcpds.getCheckoutTimeout(); }
+	
+    public String getConnectionTesterClassName()
+    { return wcpds.getConnectionTesterClassName(); }
+	
+    public void setConnectionTesterClassName( String connectionTesterClassName ) throws PropertyVetoException
+    { wcpds.setConnectionTesterClassName( connectionTesterClassName ); }
+	
+    public boolean isForceIgnoreUnresolvedTransactions()
+    { return wcpds.isForceIgnoreUnresolvedTransactions(); }
+	
+    public void setForceIgnoreUnresolvedTransactions( boolean forceIgnoreUnresolvedTransactions )
+    { wcpds.setForceIgnoreUnresolvedTransactions( forceIgnoreUnresolvedTransactions ); }
+	
+    public String getIdentityToken()
+    { return identityToken; }
+	
+    public void setIdentityToken(String identityToken)
+    { this.identityToken = identityToken; }
+	
+    public void setIdleConnectionTestPeriod( int idleConnectionTestPeriod )
+    { wcpds.setIdleConnectionTestPeriod( idleConnectionTestPeriod ); }
+    
+    public int getIdleConnectionTestPeriod()
+    { return wcpds.getIdleConnectionTestPeriod(); }
+	
+    public int getInitialPoolSize()
+    { return wcpds.getInitialPoolSize(); }
+	
+    public void setInitialPoolSize( int initialPoolSize )
+    { wcpds.setInitialPoolSize( initialPoolSize ); }
+
+    public int getMaxIdleTime()
+    { return wcpds.getMaxIdleTime(); }
+	
+    public void setMaxIdleTime( int maxIdleTime )
+    { wcpds.setMaxIdleTime( maxIdleTime ); }
+	
+    public int getMaxPoolSize()
+    { return wcpds.getMaxPoolSize(); }
+	
+    public void setMaxPoolSize( int maxPoolSize )
+    { wcpds.setMaxPoolSize( maxPoolSize ); }
+	
+    public int getMaxStatements()
+    { return wcpds.getMaxStatements(); }
+	
+    public void setMaxStatements( int maxStatements )
+    { wcpds.setMaxStatements( maxStatements ); }
+	
+    public int getMaxStatementsPerConnection()
+    { return wcpds.getMaxStatementsPerConnection(); }
+	
+    public void setMaxStatementsPerConnection( int mspc )
+    { wcpds.setMaxStatementsPerConnection( mspc ); }
+	
+    public int getMinPoolSize()
+    { return wcpds.getMinPoolSize(); }
+	
+    public void setMinPoolSize( int minPoolSize )
+    { wcpds.setMinPoolSize( minPoolSize ); }
+	
+    public String getPreferredTestQuery()
+    { return wcpds.getPreferredTestQuery(); }
+	
+    public void setPreferredTestQuery( String ptq )
+    { wcpds.setPreferredTestQuery( ptq ); }
+	
+    public int getPropertyCycle()
+    { return wcpds.getPropertyCycle(); }
+	
+    public void setPropertyCycle( int propertyCycle )
+    { wcpds.setPropertyCycle( propertyCycle ); }
+	
+    public boolean isTestConnectionOnCheckin()
+    { return wcpds.isTestConnectionOnCheckin(); }
+	
+    public void setTestConnectionOnCheckin( boolean testConnectionOnCheckin )
+    { wcpds.setTestConnectionOnCheckin( testConnectionOnCheckin ); }
+	
+    public boolean isTestConnectionOnCheckout()
+    { return wcpds.isTestConnectionOnCheckout(); }
+	
+    public void setTestConnectionOnCheckout( boolean testConnectionOnCheckout )
+    { wcpds.setTestConnectionOnCheckout( testConnectionOnCheckout ); }
+	
+    public boolean isUsesTraditionalReflectiveProxies()
+    { return wcpds.isUsesTraditionalReflectiveProxies(); }
+	
+    public void setUsesTraditionalReflectiveProxies( boolean utrp )
+    { wcpds.setUsesTraditionalReflectiveProxies( utrp ); }
+	
+    public String getFactoryClassLocation()
+    { return jrfds.getFactoryClassLocation(); }
+
+    public void setFactoryClassLocation( String factoryClassLocation )
+    { 
+	jrfds.setFactoryClassLocation( factoryClassLocation );
+	wcpds.setFactoryClassLocation( factoryClassLocation );
+    }
+
+    final static JavaBeanReferenceMaker referenceMaker = new JavaBeanReferenceMaker();
+    
+    static
+    {
+	referenceMaker.setFactoryClassName( C3P0JavaBeanObjectFactory.class.getName() );
+	referenceMaker.addReferenceProperty("acquireIncrement");
+	referenceMaker.addReferenceProperty("acquireRetryAttempts");
+	referenceMaker.addReferenceProperty("acquireRetryDelay");
+	referenceMaker.addReferenceProperty("autoCommitOnClose");
+	referenceMaker.addReferenceProperty("automaticTestTable");
+	referenceMaker.addReferenceProperty("checkoutTimeout");
+	referenceMaker.addReferenceProperty("connectionTesterClassName");
+	referenceMaker.addReferenceProperty("factoryClassLocation");
+	referenceMaker.addReferenceProperty("forceIgnoreUnresolvedTransactions");
+	referenceMaker.addReferenceProperty("idleConnectionTestPeriod");
+	referenceMaker.addReferenceProperty("identityToken");
+	referenceMaker.addReferenceProperty("initialPoolSize");
+	referenceMaker.addReferenceProperty("jndiEnv");
+	referenceMaker.addReferenceProperty("jndiLookupCaching");
+	referenceMaker.addReferenceProperty("jndiName");
+	referenceMaker.addReferenceProperty("maxIdleTime");
+	referenceMaker.addReferenceProperty("maxPoolSize");
+	referenceMaker.addReferenceProperty("maxStatements");
+	referenceMaker.addReferenceProperty("maxStatementsPerConnection");
+	referenceMaker.addReferenceProperty("minPoolSize");
+	referenceMaker.addReferenceProperty("preferredTestQuery");
+	referenceMaker.addReferenceProperty("propertyCycle");
+	referenceMaker.addReferenceProperty("testConnectionOnCheckin");
+	referenceMaker.addReferenceProperty("testConnectionOnCheckout");
+	referenceMaker.addReferenceProperty("usesTraditionalReflectiveProxies");
+    }
+    
+    public Reference getReference() throws NamingException
+    { return referenceMaker.createReference( this ); }
+
+    //implementation of javax.sql.ConnectionPoolDataSource
+    public PooledConnection getPooledConnection()
+	throws SQLException
+    { return wcpds.getPooledConnection(); } 
+ 
+    public PooledConnection getPooledConnection(String user, String password)
+	throws SQLException
+    { return wcpds.getPooledConnection( user, password ); } 
+ 
+    public PrintWriter getLogWriter()
+	throws SQLException
+    { return wcpds.getLogWriter(); }
+
+    public void setLogWriter(PrintWriter out)
+	throws SQLException
+    { wcpds.setLogWriter( out ); }
+
+    public void setLoginTimeout(int seconds)
+	throws SQLException
+    { wcpds.setLoginTimeout( seconds ); }
+
+    public int getLoginTimeout()
+	throws SQLException
+    { return wcpds.getLoginTimeout(); }
+
+    public String toString()
+    {
+	StringBuffer sb = new StringBuffer(512);
+	sb.append( super.toString() );
+	sb.append(" [");
+	try { BeansUtils.appendPropNamesAndValues( sb, this, IGNORE_PROPS ); }
+	catch (Exception e)
+	    {
+		//e.printStackTrace();
+		if ( Debug.DEBUG && logger.isLoggable( MLevel.FINE ) )
+		    logger.log( MLevel.FINE, "An exception occurred while extracting property names and values for toString()", e);
+		sb.append( e.toString() ); 
+	    }
+	sb.append("]");
+	return sb.toString();
+    }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/JndiRefForwardingDataSource.java b/src/classes/com/mchange/v2/c3p0/JndiRefForwardingDataSource.java
new file mode 100644
index 0000000..03e0f9e
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/JndiRefForwardingDataSource.java
@@ -0,0 +1,169 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.VetoableChangeListener;
+import java.beans.PropertyVetoException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Hashtable;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.InitialContext;
+import javax.sql.DataSource;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import com.mchange.v2.sql.SqlUtils;
+import com.mchange.v2.c3p0.impl.JndiRefDataSourceBase;
+
+final class JndiRefForwardingDataSource extends JndiRefDataSourceBase implements DataSource
+{
+    final static MLogger logger = MLog.getLogger( JndiRefForwardingDataSource.class );
+
+    //MT: protected by this' lock in all cases
+    transient DataSource cachedInner;
+
+    public JndiRefForwardingDataSource()
+    { this( true ); }
+
+    public JndiRefForwardingDataSource( boolean autoregister )
+    {
+	super( autoregister );
+	setUpPropertyListeners();
+    }
+
+    private void setUpPropertyListeners()
+    {
+	VetoableChangeListener l = new VetoableChangeListener()
+	    {
+		public void vetoableChange( PropertyChangeEvent evt ) throws PropertyVetoException
+		{
+		    Object val = evt.getNewValue();
+		    if ( "jndiName".equals( evt.getPropertyName() ) )
+			{
+			    if (! (val instanceof Name || val instanceof String) )
+				throw new PropertyVetoException("jndiName must be a String or a javax.naming.Name", evt);
+			}
+		}
+	    };
+	this.addVetoableChangeListener( l );
+
+	PropertyChangeListener pcl = new PropertyChangeListener()
+	    {
+		public void propertyChange( PropertyChangeEvent evt )
+		{ cachedInner = null; }
+	    };
+	this.addPropertyChangeListener( pcl );
+    }
+
+    //MT: called only from inner(), effectively synchrtonized
+    private DataSource dereference() throws SQLException
+    {
+	Object jndiName = this.getJndiName();
+	Hashtable jndiEnv = this.getJndiEnv();
+	try
+	    {
+		InitialContext ctx;
+		if (jndiEnv != null)
+		    ctx = new InitialContext( jndiEnv );
+		else
+		    ctx = new InitialContext();
+		if (jndiName instanceof String)
+		    return (DataSource) ctx.lookup( (String) jndiName );
+		else if (jndiName instanceof Name)
+		    return (DataSource) ctx.lookup( (Name) jndiName );
+		else
+		    throw new SQLException("Could not find ConnectionPoolDataSource with " +
+					   "JNDI name: " + jndiName);
+	    }
+	catch( NamingException e )
+	    {
+		//e.printStackTrace();
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "An Exception occurred while trying to look up a target DataSource via JNDI!", e );
+		throw SqlUtils.toSQLException( e ); 
+	    }
+    }
+
+    private synchronized DataSource inner() throws SQLException
+    {
+	if (cachedInner != null)
+	    return cachedInner;
+	else
+	    {
+		DataSource out = dereference();
+		if (this.isCaching())
+		    cachedInner = out;
+		return out;
+	    }
+    }
+
+    public Connection getConnection() throws SQLException
+    { return inner().getConnection(); }
+
+    public Connection getConnection(String username, String password) throws SQLException
+    { return inner().getConnection( username, password );  }
+
+    public PrintWriter getLogWriter() throws SQLException
+    { return inner().getLogWriter(); }
+
+    public void setLogWriter(PrintWriter out) throws SQLException
+    { inner().setLogWriter( out ); }
+
+    public int getLoginTimeout() throws SQLException
+    { return inner().getLoginTimeout(); }
+
+    public void setLoginTimeout(int seconds) throws SQLException
+    { inner().setLoginTimeout( seconds ); }
+
+    // serialization stuff -- set up bound/constrained property event handlers on deserialization
+    private static final long serialVersionUID = 1;
+    private static final short VERSION = 0x0001;
+	
+    private void writeObject( ObjectOutputStream oos ) throws IOException
+    {
+	oos.writeShort( VERSION );
+    }
+	
+    private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
+    {
+	short version = ois.readShort();
+	switch (version)
+	    {
+	    case VERSION:
+		setUpPropertyListeners();
+		break;
+	    default:
+		throw new IOException("Unsupported Serialized Version: " + version);
+	    }
+    }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/PoolBackedDataSource.java b/src/classes/com/mchange/v2/c3p0/PoolBackedDataSource.java
new file mode 100644
index 0000000..ff8c61e
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/PoolBackedDataSource.java
@@ -0,0 +1,39 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource;
+
+public final class PoolBackedDataSource extends AbstractPoolBackedDataSource implements PooledDataSource
+{
+    public PoolBackedDataSource( boolean autoregister )
+    { super( autoregister ); }
+
+    public PoolBackedDataSource()
+    { this( true ); }
+
+    public PoolBackedDataSource(String configName)
+    { super( configName ); }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/PoolBackedDataSourceFactory.java b/src/classes/com/mchange/v2/c3p0/PoolBackedDataSourceFactory.java
new file mode 100644
index 0000000..92be4b1
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/PoolBackedDataSourceFactory.java
@@ -0,0 +1,670 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.SQLException;
+import javax.sql.DataSource;
+import com.mchange.v2.sql.SqlUtils;
+
+/**
+ *  A class offering Factory methods for creating DataSources backed
+ *  by Connection and Statement Pools.
+ *
+ *  @deprecated Use the new factories in {@link com.mchange.v2.c3p0.DataSources}. See examples.
+ *
+ */
+public final class PoolBackedDataSourceFactory
+{
+    /**
+     *  Creates a pool-backed DataSource that implements Referenceable
+     *  for binding to JNDI name services. For this to work,
+     *  <TT>unpooledDataSource</TT> must also implement Referenceable.
+     *
+     *  @param unpooledDataSource an unpooledDataSource to use as the
+     *         primary source for connections.
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     *  @param factoryLocation a codebase url where JNDI clients can find the  
+     *         c3p0 libraries. Use null if clients will be expected to have the
+     *         libraries available locally.
+     *
+     *  @deprecated all implementations are now both Referenceable and Serializable.
+     *              use create()
+     */
+    public static DataSource createReferenceable( DataSource unpooledDataSource,
+						  int minPoolSize,
+						  int maxPoolSize,
+						  int acquireIncrement,
+						  int maxIdleTime,
+						  int maxStatements,
+						  String factoryLocation ) throws SQLException
+    {
+	try
+	    {
+		WrapperConnectionPoolDataSource cpds = new WrapperConnectionPoolDataSource();
+		cpds.setNestedDataSource(unpooledDataSource);
+		cpds.setMinPoolSize( minPoolSize );
+		cpds.setMaxPoolSize( maxPoolSize );
+		cpds.setAcquireIncrement( acquireIncrement );
+		cpds.setMaxIdleTime( maxIdleTime );
+		cpds.setMaxStatements( maxStatements );
+		cpds.setFactoryClassLocation( factoryLocation );
+		
+		
+		PoolBackedDataSource out = new PoolBackedDataSource();
+		out.setConnectionPoolDataSource( cpds );
+		return out;
+	    }
+	catch (Exception e)
+	    { throw SqlUtils.toSQLException( e ); }
+    }
+
+    /**
+     *  Creates a pool-backed DataSource that uses default pool parameters and
+     *  implements Referenceable
+     *  for binding to JNDI name services. For this to work,
+     *  <TT>unpooledDataSource</TT> must also implement Referenceable.
+     *
+     *  @param unpooledDataSource an unpooledDataSource to use as the
+     *         primary source for connections.
+     *  @param factoryLocation a codebase url where JNDI clients can find the  
+     *         c3p0 libraries. Use null if clients will be expected to have the
+     *         libraries available locally.
+     *
+     *  @deprecated all implementations are now both Referenceable and Serializable.
+     *              use create()
+     */
+    public static DataSource createReferenceable( DataSource unpooledDataSource,
+						  String factoryLocation ) 
+	throws SQLException
+    {
+	try
+	    {
+		WrapperConnectionPoolDataSource cpds = new WrapperConnectionPoolDataSource();
+		cpds.setNestedDataSource(unpooledDataSource);
+		cpds.setFactoryClassLocation( factoryLocation );
+		
+		PoolBackedDataSource out = new PoolBackedDataSource();
+		out.setConnectionPoolDataSource( cpds );
+		return out;
+	    }
+	catch (Exception e)
+	    { throw SqlUtils.toSQLException( e ); }
+    }
+
+    /**
+     *  Creates a pool-backed DataSource that implements Referenceable.
+     *
+     *  @param jdbcDriverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     *  @param factoryLocation a codebase url where JNDI clients can find the  
+     *         c3p0 libraries. Use null if clients will be expected to have the
+     *         libraries available locally.
+     *
+     *  @deprecated all implementations are now both Referenceable and Serializable.
+     *              use create()
+     */
+    public static DataSource createReferenceable(String jdbcDriverClass, 
+						 String jdbcUrl,
+						 String user,
+						 String password,
+						 int minPoolSize,
+						 int maxPoolSize,
+						 int acquireIncrement,
+						 int maxIdleTime,
+						 int maxStatements,
+						 String factoryLocation ) throws SQLException
+    {
+	DataSource nested = DriverManagerDataSourceFactory.create( jdbcDriverClass, 
+								   jdbcUrl, 
+								   user, 
+								   password );
+	return createReferenceable( nested, 
+				    minPoolSize,
+				    maxPoolSize,
+				    acquireIncrement,
+				    maxIdleTime,
+				    maxStatements,
+				    factoryLocation );
+    }
+
+    /**
+     *  Creates a pool-backed DataSource that implements Referenceable and uses
+     *  default pooling parameters.
+     *
+     *  @param jdbcDriverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     *  @param factoryLocation a codebase url where JNDI clients can find the  
+     *         c3p0 libraries. Use null if clients will be expected to have the
+     *         libraries available locally.
+     *
+     *  @deprecated all implementations are now both Referenceable and Serializable.
+     *              use create()
+     */
+    public static DataSource createReferenceable(String jdbcDriverClass, 
+						 String jdbcUrl,
+						 String user,
+						 String password,
+						 String factoryLocation ) 
+	throws SQLException
+    {
+	DataSource nested = DriverManagerDataSourceFactory.create( jdbcDriverClass, 
+								   jdbcUrl, 
+								   user, 
+								   password );
+	return createReferenceable( nested, 
+				    factoryLocation );
+    }
+
+    /**
+     *  Creates a pool-backed DataSource that implements Serializable
+     *  for binding to JNDI name services. For this to work,
+     *  <TT>unpooledDataSource</TT> must also implement Serializable.
+     *
+     *  @param unpooledDataSource an unpooledDataSource to use as the
+     *         primary source for connections.
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     *
+     *  @deprecated all implementations are now both Referenceable and Serializable.
+     *              use create()
+     */
+    public static DataSource createSerializable( DataSource unpooledDataSource,
+						 int minPoolSize,
+						 int maxPoolSize,
+						 int acquireIncrement,
+						 int maxIdleTime,
+						 int maxStatements) 
+	throws SQLException
+    {
+	try
+	    {
+		WrapperConnectionPoolDataSource cpds = new WrapperConnectionPoolDataSource();
+		cpds.setNestedDataSource(unpooledDataSource);
+		cpds.setMinPoolSize( minPoolSize );
+		cpds.setMaxPoolSize( maxPoolSize );
+		cpds.setAcquireIncrement( acquireIncrement );
+		cpds.setMaxIdleTime( maxIdleTime );
+		cpds.setMaxStatements( maxStatements );
+		
+		PoolBackedDataSource out = new PoolBackedDataSource();
+		out.setConnectionPoolDataSource( cpds );
+		return out;
+	    }
+	catch (Exception e)
+	    { throw SqlUtils.toSQLException( e ); }
+    }
+
+    /**
+     *  Creates a pool-backed DataSource that uses default pool parameters and
+     *  implements Serializable
+     *  for binding to JNDI name services. For this to work,
+     *  <TT>unpooledDataSource</TT> must also implement Serializable.
+     *
+     *  @param unpooledDataSource an unpooledDataSource to use as the
+     *         primary source for connections.
+     *
+     *  @deprecated all implementations are now both Referenceable and Serializable.
+     *              use create()
+     */
+    public static DataSource createSerializable( DataSource unpooledDataSource ) throws SQLException
+    {
+	try
+	    {
+		WrapperConnectionPoolDataSource cpds = new WrapperConnectionPoolDataSource();
+		cpds.setNestedDataSource(unpooledDataSource);
+		
+		PoolBackedDataSource out = new PoolBackedDataSource();
+		out.setConnectionPoolDataSource( cpds );
+		return out;
+	    }
+	catch (Exception e)
+	    { throw SqlUtils.toSQLException( e ); }
+    }
+
+
+    /**
+     *  Creates a pool-backed DataSource that implements Serializable.
+     *
+     *  @param jdbcDriverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     *
+     *  @deprecated all implementations are now both Referenceable and Serializable.
+     *              use create()
+     */
+    public static DataSource createSerializable( String jdbcDriverClass,
+						 String jdbcUrl,
+						 String user,
+						 String password,
+						 int minPoolSize,
+						 int maxPoolSize,
+						 int acquireIncrement,
+						 int maxIdleTime,
+						 int maxStatements) 
+	throws SQLException
+    {
+	DataSource nested = DriverManagerDataSourceFactory.create( jdbcDriverClass, 
+								   jdbcUrl, 
+								   user, 
+								   password );
+	return createSerializable( nested, 
+				   minPoolSize,
+				   maxPoolSize,
+				   acquireIncrement,
+				   maxIdleTime,
+				   maxStatements);
+    }
+
+    /**
+     *  Creates a pool-backed DataSource that implements Serializable and uses
+     *  default pooling parameters.
+     *
+     *  @param jdbcDriverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     *
+     *  @deprecated all implementations are now both Referenceable and Serializable.
+     *              use create()
+     */
+    public static DataSource createSerializable( String jdbcDriverClass,
+						 String jdbcUrl,
+						 String user,
+						 String password) 
+	throws SQLException
+    {
+	DataSource nested = DriverManagerDataSourceFactory.create( jdbcDriverClass, 
+								   jdbcUrl, 
+								   user, 
+								   password );
+	return createSerializable( nested );
+    }
+
+    /**
+     *  Creates a pool-backed DataSource using <TT>unpooledDataSource</TT>
+     *  as its source for Connections. Not necessarily suitable for JNDI binding.
+     *
+     *  @param unpooledDataSource an unpooledDataSource to use as the
+     *         primary source for connections.
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     *  @param factoryLocation a codebase url where JNDI clients can find the  
+     *         c3p0 libraries. Use null if clients will be expected to have the
+     *         libraries available locally. Used only if the JNDI service prefers
+     *         References to Serialized Objects when Objects are bound.
+     */
+    public static DataSource create( DataSource unpooledDataSource,
+				     int minPoolSize,
+				     int maxPoolSize,
+				     int acquireIncrement,
+				     int maxIdleTime,
+				     int maxStatements,
+				     String factoryLocation) throws SQLException
+    {
+	return createReferenceable( unpooledDataSource,
+				    minPoolSize,
+				    maxPoolSize,
+				    acquireIncrement,
+				    maxIdleTime,
+				    maxStatements,  
+				    factoryLocation );
+    }
+
+    /**
+     *  Creates a pool-backed DataSource using <TT>unpooledDataSource</TT>
+     *  as its source for Connections. Not necessarily suitable for JNDI binding.
+     *
+     *  @param unpooledDataSource an unpooledDataSource to use as the
+     *         primary source for connections.
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     */
+    public static DataSource create( DataSource unpooledDataSource,
+				     int minPoolSize,
+				     int maxPoolSize,
+				     int acquireIncrement,
+				     int maxIdleTime,
+				     int maxStatements ) throws SQLException
+    {
+	return createReferenceable( unpooledDataSource,
+				    minPoolSize,
+				    maxPoolSize,
+				    acquireIncrement,
+				    maxIdleTime,
+				    maxStatements,  
+				    null );
+    }
+
+    /**
+     *  Creates a pool-backed DataSource using <TT>unpooledDataSource</TT>
+     *  as its source for Connections and default values for pool params. 
+     *
+     *  @param unpooledDataSource an unpooledDataSource to use as the
+     *         primary source for connections.
+     */
+    public static DataSource create( DataSource unpooledDataSource ) throws SQLException
+    { return createSerializable( unpooledDataSource ); }
+
+    /**
+     *  Creates a pool-backed DataSource.
+     *
+     *  @param jdbcDriverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     *  @param factoryLocation a codebase url where JNDI clients can find the  
+     *         c3p0 libraries. Use null if clients will be expected to have the
+     *         libraries available locally. Used only if the JNDI service prefers
+     *         References to Serialized Objects when Objects are bound.
+     */
+    public static DataSource create( String jdbcDriverClass,
+				     String jdbcUrl,
+				     String user,
+				     String password,
+				     int minPoolSize,
+				     int maxPoolSize,
+				     int acquireIncrement,
+				     int maxIdleTime,
+				     int maxStatements,
+				     String factoryLocation ) 
+	throws SQLException
+    {
+	return createReferenceable( jdbcDriverClass,
+				    jdbcUrl,
+				    user,
+				    password,
+				    minPoolSize,
+				    maxPoolSize,
+				    acquireIncrement,
+				    maxIdleTime,
+				    maxStatements,
+				    factoryLocation );
+    }
+
+    /**
+     *  Creates a pool-backed DataSource.
+     *
+     *  @param jdbcDriverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     */
+    public static DataSource create( String jdbcDriverClass,
+				     String jdbcUrl,
+				     String user,
+				     String password,
+				     int minPoolSize,
+				     int maxPoolSize,
+				     int acquireIncrement,
+				     int maxIdleTime,
+				     int maxStatements )
+	throws SQLException
+    {
+	return createReferenceable( jdbcDriverClass,
+				    jdbcUrl,
+				    user,
+				    password,
+				    minPoolSize,
+				    maxPoolSize,
+				    acquireIncrement,
+				    maxIdleTime,
+				    maxStatements,
+				    null );
+    }
+    /**
+     *  Creates a pool-backed DataSource.
+     *
+     *  <P>Warning: If you use this method, you must make sure a JDBC driver
+     *  capable of resolving <TT>jdbcUrl</TT> has been preloaded!</P>
+     *
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     *  @param factoryLocation a codebase url where JNDI clients can find the  
+     *         c3p0 libraries. Use null if clients will be expected to have the
+     *         libraries available locally. Used only if the JNDI service prefers
+     *         References to Serialized Objects when Objects are bound.
+     */
+    public static DataSource create( String jdbcUrl,
+				     String user,
+				     String password,
+				     int minPoolSize,
+				     int maxPoolSize,
+				     int acquireIncrement,
+				     int maxIdleTime,
+				     int maxStatements,
+				     String factoryLocation ) 
+	throws SQLException
+    {
+	return create( null,
+		       jdbcUrl,
+		       user,
+		       password,
+		       minPoolSize,
+		       maxPoolSize,
+		       acquireIncrement,
+		       maxIdleTime,
+		       maxStatements,
+		       factoryLocation );
+    }
+
+    /**
+     *  Creates a pool-backed DataSource.
+     *
+     *  <P>Warning: If you use this method, you must make sure a JDBC driver
+     *  capable of resolving <TT>jdbcUrl</TT> has been preloaded!</P>
+     *
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     *  @param minPoolSize the minimum (and starting) number of Connections
+     *         that should be held in the pool.
+     *  @param maxPoolSize the maximum number of Connections
+     *         that should be held in the pool.
+     *  @param acquireIncrement the number of Connections that should be
+     *         acquired at a time when the pool runs out of Connections
+     *  @param maxIdleTime the maximum number of seconds a Connection should be
+     *         allowed to remain idle before it is expired from the pool.
+     *         A value of 0 means Connections never expire.
+     *  @param maxStatements the maximum number of PreparedStatements that should 
+     *         be cached by this pool. A value of 0 means that Statement caching
+     *         should be disabled.
+     */
+    public static DataSource create( String jdbcUrl,
+				     String user,
+				     String password,
+				     int minPoolSize,
+				     int maxPoolSize,
+				     int acquireIncrement,
+				     int maxIdleTime,
+				     int maxStatements )
+	throws SQLException
+    {
+	return create( null,
+		       jdbcUrl,
+		       user,
+		       password,
+		       minPoolSize,
+		       maxPoolSize,
+		       acquireIncrement,
+		       maxIdleTime,
+		       maxStatements,
+		       null );
+    }
+
+    /**
+     *  Creates a pool-backed DataSource using default values for pool parameters.
+     *  Not necessarily suitable for JNDI binding.
+     *
+     *  @param jdbcDriverClass a jdbc driver class that can resolve <TT>jdbcUrl</TT>.
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     */
+    public static DataSource create( String jdbcDriverClass,
+				     String jdbcUrl,
+				     String user,
+				     String password) throws SQLException
+    {
+	return createSerializable( jdbcDriverClass,
+				   jdbcUrl,
+				   user,
+				   password );
+    }
+
+    /**
+     *  Creates a pool-backed DataSource using default pool parameters.
+     *
+     *
+     *  <P>Warning: If you use this method, you must make sure a JDBC driver
+     *  capable of resolving <TT>jdbcUrl</TT> has been preloaded!</P>
+     *
+     *  @param jdbcUrl the jdbcUrl of the RDBMS that Connections should be made to.
+     *  @param user a username (may be null) for authentication to the RDBMS
+     *  @param password a password (may be null) for authentication to the RDBMS
+     */
+    public static DataSource create( String jdbcUrl,
+				     String user,
+				     String password) 
+	throws SQLException
+    {
+	return create( null, 
+		       jdbcUrl,
+		       user,
+		       password );
+    }
+ 
+    private PoolBackedDataSourceFactory()
+    {}
+}
+
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/PoolConfig.java b/src/classes/com/mchange/v2/c3p0/PoolConfig.java
new file mode 100644
index 0000000..bbba8f2
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/PoolConfig.java
@@ -0,0 +1,635 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.util.Properties;
+import java.io.InputStream;
+import java.io.IOException;
+import com.mchange.v1.io.InputStreamUtils;
+import com.mchange.v2.c3p0.impl.C3P0Defaults;
+import com.mchange.v2.cfg.MultiPropertiesConfig;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+
+/**
+ *  <p>Encapsulates all the configuration information required by a c3p0 pooled DataSource.</p>
+ *
+ *  <p>Newly constructed PoolConfig objects are preset with default values,
+ *  which you can define yourself (see below),
+ *  or you can rely on c3p0's built-in defaults. Just create a PoolConfig object, and change only the
+ *  properties you care about. Then pass it to the {@link com.mchange.v2.c3p0.DataSources#pooledDataSource(javax.sql.DataSource, com.mchange.v2.c3p0.PoolConfig)}
+ *  method, and you're off!</p>
+ *
+ *  <p>For those interested in the details, configuration properties can be specified in several ways:</p>
+ *  <ol>
+ *    <li>Any property can be set explicitly by calling the corresponding method on a PoolConfig object.</li>
+ *    <li>Any property will default to a value defined by a System Property, using the property name shown the table below.</li>
+ *    <li>Any property not set in either of the above ways will default to a value found in a user-supplied Java properties file,
+ *        which may be placed in the resource path of
+ *        the ClassLoader that loaded the c3p0 libraries under the name <tt>/c3p0.properties</tt>.</li>
+ *    <li>Any property not set in any of the above ways will be defined according c3p0's built-in defaults.</li>
+ *  </ol>
+ *
+ *  <p><i>Please see c3p0's main documentation for a description of all available parameters.</i></p>
+ *
+ *  @deprecated as of c3p0-0.9.1. To manipulate config programmaticall, please use ComboPooledDataSource
+ *
+ */
+public final class PoolConfig
+{
+    final static MLogger logger;
+
+    public final static String INITIAL_POOL_SIZE                    = "c3p0.initialPoolSize"; 
+    public final static String MIN_POOL_SIZE                        = "c3p0.minPoolSize";
+    public final static String MAX_POOL_SIZE                        = "c3p0.maxPoolSize";
+    public final static String IDLE_CONNECTION_TEST_PERIOD          = "c3p0.idleConnectionTestPeriod";
+    public final static String MAX_IDLE_TIME                        = "c3p0.maxIdleTime";
+    public final static String PROPERTY_CYCLE                       = "c3p0.propertyCycle";
+    public final static String MAX_STATEMENTS                       = "c3p0.maxStatements";
+    public final static String MAX_STATEMENTS_PER_CONNECTION        = "c3p0.maxStatementsPerConnection";
+    public final static String CHECKOUT_TIMEOUT                     = "c3p0.checkoutTimeout";
+    public final static String ACQUIRE_INCREMENT                    = "c3p0.acquireIncrement";
+    public final static String ACQUIRE_RETRY_ATTEMPTS               = "c3p0.acquireRetryAttempts";
+    public final static String ACQUIRE_RETRY_DELAY                  = "c3p0.acquireRetryDelay";
+    public final static String BREAK_AFTER_ACQUIRE_FAILURE          = "c3p0.breakAfterAcquireFailure";
+    public final static String USES_TRADITIONAL_REFLECTIVE_PROXIES  = "c3p0.usesTraditionalReflectiveProxies";
+    public final static String TEST_CONNECTION_ON_CHECKOUT          = "c3p0.testConnectionOnCheckout";
+    public final static String TEST_CONNECTION_ON_CHECKIN           = "c3p0.testConnectionOnCheckin";
+    public final static String CONNECTION_TESTER_CLASS_NAME         = "c3p0.connectionTesterClassName";
+    public final static String AUTOMATIC_TEST_TABLE                 = "c3p0.automaticTestTable";
+    public final static String AUTO_COMMIT_ON_CLOSE                 = "c3p0.autoCommitOnClose";
+    public final static String FORCE_IGNORE_UNRESOLVED_TRANSACTIONS = "c3p0.forceIgnoreUnresolvedTransactions";
+    public final static String NUM_HELPER_THREADS                   = "c3p0.numHelperThreads";
+    public final static String PREFERRED_TEST_QUERY                 = "c3p0.preferredTestQuery";
+    public final static String FACTORY_CLASS_LOCATION               = "c3p0.factoryClassLocation";
+    
+    public final static String DEFAULT_CONFIG_RSRC_PATH = "/c3p0.properties";
+    
+    final static PoolConfig DEFAULTS;
+
+    static
+    {
+	logger = MLog.getLogger( PoolConfig.class );
+
+	Properties rsrcProps = findResourceProperties();
+	PoolConfig rsrcDefaults = extractConfig( rsrcProps, null );
+
+	Properties sysProps;
+	try
+	    { sysProps = System.getProperties(); }
+	catch ( SecurityException e )
+	    {
+		if (logger.isLoggable(MLevel.WARNING))
+		    logger.log(MLevel.WARNING, 
+			       "Read of system Properties blocked -- ignoring any c3p0 configuration via System properties! " +
+			       "(But any configuration via a c3p0.properties file is still okay!)", 
+			       e);
+		sysProps = new Properties(); //TODO -- an alternative approach to getting c3p0-specific sysprops if allowed
+	    }
+	DEFAULTS = extractConfig( sysProps, rsrcDefaults );
+    }
+
+    public static int defaultNumHelperThreads()
+    { return DEFAULTS.getNumHelperThreads(); }
+
+    public static String defaultPreferredTestQuery()
+    { return DEFAULTS.getPreferredTestQuery(); }
+
+    public static String defaultFactoryClassLocation()
+    { return DEFAULTS.getFactoryClassLocation(); }
+
+    public static int defaultMaxStatements()
+    { return DEFAULTS.getMaxStatements(); }
+
+    public static int defaultMaxStatementsPerConnection()
+    { return DEFAULTS.getMaxStatementsPerConnection(); }
+
+    public static int defaultInitialPoolSize()
+    { return DEFAULTS.getInitialPoolSize(); }
+
+    public static int defaultMinPoolSize()
+    { return DEFAULTS.getMinPoolSize(); }
+
+    public static int defaultMaxPoolSize()
+    { return DEFAULTS.getMaxPoolSize(); }
+
+    public static int defaultIdleConnectionTestPeriod()
+    { return DEFAULTS.getIdleConnectionTestPeriod(); }
+
+    public static int defaultMaxIdleTime()
+    { return DEFAULTS.getMaxIdleTime(); }
+
+    public static int defaultPropertyCycle()
+    { return DEFAULTS.getPropertyCycle(); }
+
+    public static int defaultCheckoutTimeout()
+    { return DEFAULTS.getCheckoutTimeout(); }
+
+    public static int defaultAcquireIncrement()
+    { return DEFAULTS.getAcquireIncrement(); }
+
+    public static int defaultAcquireRetryAttempts()
+    { return DEFAULTS.getAcquireRetryAttempts(); }
+
+    public static int defaultAcquireRetryDelay()
+    { return DEFAULTS.getAcquireRetryDelay(); }
+
+    public static boolean defaultBreakAfterAcquireFailure()
+    { return DEFAULTS.isBreakAfterAcquireFailure(); }
+
+    public static String defaultConnectionTesterClassName()
+    { return DEFAULTS.getConnectionTesterClassName(); }
+
+    public static String defaultAutomaticTestTable()
+    { return DEFAULTS.getAutomaticTestTable(); }
+
+    public static boolean defaultTestConnectionOnCheckout()
+    { return DEFAULTS.isTestConnectionOnCheckout(); }
+
+    public static boolean defaultTestConnectionOnCheckin()
+    { return DEFAULTS.isTestConnectionOnCheckin(); }
+
+    public static boolean defaultAutoCommitOnClose()
+    { return DEFAULTS.isAutoCommitOnClose(); }
+
+    public static boolean defaultForceIgnoreUnresolvedTransactions()
+    { return DEFAULTS.isAutoCommitOnClose(); }
+
+    public static boolean defaultUsesTraditionalReflectiveProxies()
+    { return DEFAULTS.isUsesTraditionalReflectiveProxies(); }
+
+
+    int     maxStatements;
+    int     maxStatementsPerConnection;
+    int     initialPoolSize;
+    int     minPoolSize;
+    int     maxPoolSize;
+    int     idleConnectionTestPeriod;
+    int     maxIdleTime;
+    int     propertyCycle;
+    int     checkoutTimeout;
+    int     acquireIncrement;
+    int     acquireRetryAttempts;
+    int     acquireRetryDelay;
+    boolean breakAfterAcquireFailure;
+    boolean testConnectionOnCheckout;
+    boolean testConnectionOnCheckin;
+    boolean autoCommitOnClose;
+    boolean forceIgnoreUnresolvedTransactions;
+    boolean usesTraditionalReflectiveProxies;
+    String  connectionTesterClassName;
+    String  automaticTestTable;
+    int     numHelperThreads;
+    String  preferredTestQuery;
+    String  factoryClassLocation;
+
+    private PoolConfig( Properties props, boolean init ) throws NumberFormatException
+    {
+	if (init)
+	    extractConfig( this, props, DEFAULTS );
+    }
+
+    public PoolConfig( Properties props ) throws NumberFormatException
+    { this( props, true ); }
+
+    public PoolConfig() throws NumberFormatException
+    { this( null, true ); }
+
+    public int getNumHelperThreads()
+    { return numHelperThreads; }
+
+    public String getPreferredTestQuery()
+    { return preferredTestQuery; }
+
+    public String getFactoryClassLocation()
+    { return factoryClassLocation; }
+
+    public int getMaxStatements()
+    { return maxStatements; }
+    
+    public int getMaxStatementsPerConnection()
+    { return maxStatementsPerConnection; }
+    
+    public int getInitialPoolSize()
+    { return initialPoolSize; }
+    
+    public int getMinPoolSize()
+    { return minPoolSize; }
+    
+    public int getMaxPoolSize()
+    { return maxPoolSize; }
+    
+    public int getIdleConnectionTestPeriod()
+    { return idleConnectionTestPeriod; }
+    
+    public int getMaxIdleTime()
+    { return maxIdleTime; }
+    
+    public int getPropertyCycle()
+    { return propertyCycle; }
+    
+    public int getAcquireIncrement()
+    { return acquireIncrement; }
+    
+    public int getCheckoutTimeout()
+    { return checkoutTimeout; }
+    
+    public int getAcquireRetryAttempts()
+    { return acquireRetryAttempts; }
+    
+    public int getAcquireRetryDelay()
+    { return acquireRetryDelay; }
+    
+    public boolean isBreakAfterAcquireFailure()
+    { return this.breakAfterAcquireFailure;	}
+
+    public boolean isUsesTraditionalReflectiveProxies()
+    { return this.usesTraditionalReflectiveProxies; }
+
+    public String getConnectionTesterClassName()
+    { return connectionTesterClassName; }
+    
+    public String getAutomaticTestTable()
+    { return automaticTestTable; }
+    
+    /**
+     * @deprecated use isTestConnectionOnCheckout
+     */
+    public boolean getTestConnectionOnCheckout()
+    { return testConnectionOnCheckout; }
+
+    public boolean isTestConnectionOnCheckout()
+    { return this.getTestConnectionOnCheckout(); }
+
+    public boolean isTestConnectionOnCheckin()
+    { return testConnectionOnCheckin; }
+
+    public boolean isAutoCommitOnClose()
+    { return this.autoCommitOnClose;	}
+
+    public boolean isForceIgnoreUnresolvedTransactions()
+    { return this.forceIgnoreUnresolvedTransactions; }
+    
+    public void setNumHelperThreads( int numHelperThreads )
+    { this.numHelperThreads = numHelperThreads;	}
+
+    public void setPreferredTestQuery( String preferredTestQuery )
+    { this.preferredTestQuery = preferredTestQuery; }
+
+    public void setFactoryClassLocation( String factoryClassLocation )
+    { this.factoryClassLocation = factoryClassLocation;	}
+
+    public void setMaxStatements( int maxStatements )
+    { this.maxStatements = maxStatements; }
+    
+    public void setMaxStatementsPerConnection( int maxStatementsPerConnection )
+    { this.maxStatementsPerConnection = maxStatementsPerConnection; }
+    
+    public void setInitialPoolSize( int initialPoolSize )
+    { this.initialPoolSize = initialPoolSize; }
+    
+    public void setMinPoolSize( int minPoolSize )
+    { this.minPoolSize = minPoolSize; }
+    
+    public void setMaxPoolSize( int maxPoolSize )
+    { this.maxPoolSize = maxPoolSize; }
+    
+    public void setIdleConnectionTestPeriod( int idleConnectionTestPeriod )
+    { this.idleConnectionTestPeriod = idleConnectionTestPeriod; }
+    
+    public void setMaxIdleTime( int maxIdleTime )
+    { this.maxIdleTime = maxIdleTime; }
+    
+    public void setPropertyCycle( int propertyCycle )
+    { this.propertyCycle = propertyCycle; }
+    
+    public void setCheckoutTimeout( int checkoutTimeout )
+    { this.checkoutTimeout = checkoutTimeout; }
+    
+    public void setAcquireIncrement( int acquireIncrement )
+    { this.acquireIncrement = acquireIncrement; }
+    
+    public void setAcquireRetryAttempts( int acquireRetryAttempts )
+    { this.acquireRetryAttempts = acquireRetryAttempts; }
+    
+    public void setAcquireRetryDelay( int acquireRetryDelay )
+    { this.acquireRetryDelay = acquireRetryDelay; }
+    
+    public void setConnectionTesterClassName( String connectionTesterClassName )
+    { this.connectionTesterClassName = connectionTesterClassName; }
+    
+    public void setAutomaticTestTable( String automaticTestTable )
+    { this.automaticTestTable = automaticTestTable; }
+    
+    public void setBreakAfterAcquireFailure( boolean breakAfterAcquireFailure )
+    { this.breakAfterAcquireFailure = breakAfterAcquireFailure; }
+    
+    public void setUsesTraditionalReflectiveProxies( boolean usesTraditionalReflectiveProxies )
+    { this.usesTraditionalReflectiveProxies = usesTraditionalReflectiveProxies; }
+    
+    public void setTestConnectionOnCheckout( boolean testConnectionOnCheckout )
+    { this.testConnectionOnCheckout = testConnectionOnCheckout; }
+    
+    public void setTestConnectionOnCheckin( boolean testConnectionOnCheckin )
+    { this.testConnectionOnCheckin = testConnectionOnCheckin; }
+    
+    public void setAutoCommitOnClose( boolean autoCommitOnClose )
+    { this.autoCommitOnClose = autoCommitOnClose;  }
+
+    public void setForceIgnoreUnresolvedTransactions( boolean forceIgnoreUnresolvedTransactions )
+    { this.forceIgnoreUnresolvedTransactions = forceIgnoreUnresolvedTransactions; }
+
+    private static PoolConfig extractConfig(Properties props, PoolConfig defaults) throws NumberFormatException
+    {
+	PoolConfig pcfg = new PoolConfig(null, false);
+	extractConfig( pcfg, props, defaults );
+	return pcfg;
+    }
+
+    private static void extractConfig(PoolConfig pcfg, Properties props, PoolConfig defaults) throws NumberFormatException
+    {
+	String maxStatementsStr                     = null;
+	String maxStatementsPerConnectionStr        = null;
+	String initialPoolSizeStr                   = null;
+	String minPoolSizeStr                       = null;
+	String maxPoolSizeStr                       = null;
+	String idleConnectionTestPeriodStr          = null;
+	String maxIdleTimeStr                       = null;
+	String propertyCycleStr                     = null;
+	String checkoutTimeoutStr                   = null;
+	String acquireIncrementStr                  = null;
+	String acquireRetryAttemptsStr              = null;
+	String acquireRetryDelayStr                 = null;
+	String breakAfterAcquireFailureStr          = null;
+	String usesTraditionalReflectiveProxiesStr  = null;
+	String testConnectionOnCheckoutStr          = null;
+	String testConnectionOnCheckinStr           = null;
+	String autoCommitOnCloseStr                 = null;
+	String forceIgnoreUnresolvedTransactionsStr = null;
+	String connectionTesterClassName            = null;
+	String automaticTestTable                   = null;
+	String numHelperThreadsStr                  = null;
+	String preferredTestQuery                   = null;
+	String factoryClassLocation                 = null;
+
+	if ( props != null )
+	    {
+		maxStatementsStr = props.getProperty(MAX_STATEMENTS);
+		maxStatementsPerConnectionStr = props.getProperty(MAX_STATEMENTS_PER_CONNECTION);
+		initialPoolSizeStr = props.getProperty(INITIAL_POOL_SIZE);
+		minPoolSizeStr = props.getProperty(MIN_POOL_SIZE);
+		maxPoolSizeStr = props.getProperty(MAX_POOL_SIZE);
+		idleConnectionTestPeriodStr = props.getProperty(IDLE_CONNECTION_TEST_PERIOD);
+		maxIdleTimeStr = props.getProperty(MAX_IDLE_TIME);
+		propertyCycleStr = props.getProperty(PROPERTY_CYCLE);
+		checkoutTimeoutStr = props.getProperty(CHECKOUT_TIMEOUT);
+		acquireIncrementStr = props.getProperty(ACQUIRE_INCREMENT);
+		acquireRetryAttemptsStr = props.getProperty(ACQUIRE_RETRY_ATTEMPTS);
+		acquireRetryDelayStr = props.getProperty(ACQUIRE_RETRY_DELAY);
+		breakAfterAcquireFailureStr = props.getProperty(BREAK_AFTER_ACQUIRE_FAILURE);
+		usesTraditionalReflectiveProxiesStr = props.getProperty(USES_TRADITIONAL_REFLECTIVE_PROXIES);
+		testConnectionOnCheckoutStr = props.getProperty(TEST_CONNECTION_ON_CHECKOUT);
+		testConnectionOnCheckinStr = props.getProperty(TEST_CONNECTION_ON_CHECKIN);
+		autoCommitOnCloseStr = props.getProperty(AUTO_COMMIT_ON_CLOSE);
+		forceIgnoreUnresolvedTransactionsStr = props.getProperty(FORCE_IGNORE_UNRESOLVED_TRANSACTIONS);
+		connectionTesterClassName = props.getProperty(CONNECTION_TESTER_CLASS_NAME);
+		automaticTestTable = props.getProperty(AUTOMATIC_TEST_TABLE);
+		numHelperThreadsStr = props.getProperty(NUM_HELPER_THREADS);
+		preferredTestQuery = props.getProperty(PREFERRED_TEST_QUERY);
+		factoryClassLocation = props.getProperty(FACTORY_CLASS_LOCATION);
+	    }
+
+	// maxStatements
+	if ( maxStatementsStr != null )
+	    pcfg.setMaxStatements( Integer.parseInt( maxStatementsStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setMaxStatements( defaults.getMaxStatements() );
+	else
+	    pcfg.setMaxStatements( C3P0Defaults.maxStatements() );
+
+	// maxStatementsPerConnection
+	if ( maxStatementsPerConnectionStr != null )
+	    pcfg.setMaxStatementsPerConnection( Integer.parseInt( maxStatementsPerConnectionStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setMaxStatementsPerConnection( defaults.getMaxStatementsPerConnection() );
+	else
+	    pcfg.setMaxStatementsPerConnection( C3P0Defaults.maxStatementsPerConnection() );
+
+	// initialPoolSize
+	if ( initialPoolSizeStr != null )
+	    pcfg.setInitialPoolSize( Integer.parseInt( initialPoolSizeStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setInitialPoolSize( defaults.getInitialPoolSize() );
+	else
+	    pcfg.setInitialPoolSize( C3P0Defaults.initialPoolSize() );
+
+	// minPoolSize
+	if ( minPoolSizeStr != null )
+	    pcfg.setMinPoolSize( Integer.parseInt( minPoolSizeStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setMinPoolSize( defaults.getMinPoolSize() );
+	else
+	    pcfg.setMinPoolSize( C3P0Defaults.minPoolSize() );
+
+	// maxPoolSize
+	if ( maxPoolSizeStr != null )
+	    pcfg.setMaxPoolSize( Integer.parseInt( maxPoolSizeStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setMaxPoolSize( defaults.getMaxPoolSize() );
+	else
+	    pcfg.setMaxPoolSize( C3P0Defaults.maxPoolSize() );
+
+	// maxIdleTime
+	if ( idleConnectionTestPeriodStr != null )
+	    pcfg.setIdleConnectionTestPeriod( Integer.parseInt( idleConnectionTestPeriodStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setIdleConnectionTestPeriod( defaults.getIdleConnectionTestPeriod() );
+	else
+	    pcfg.setIdleConnectionTestPeriod( C3P0Defaults.idleConnectionTestPeriod() );
+
+	// maxIdleTime
+	if ( maxIdleTimeStr != null )
+	    pcfg.setMaxIdleTime( Integer.parseInt( maxIdleTimeStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setMaxIdleTime( defaults.getMaxIdleTime() );
+	else
+	    pcfg.setMaxIdleTime( C3P0Defaults.maxIdleTime() );
+
+	// propertyCycle
+	if ( propertyCycleStr != null )
+	    pcfg.setPropertyCycle( Integer.parseInt( propertyCycleStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setPropertyCycle( defaults.getPropertyCycle() );
+	else
+	    pcfg.setPropertyCycle( C3P0Defaults.propertyCycle() );
+
+	// checkoutTimeout
+	if ( checkoutTimeoutStr != null )
+	    pcfg.setCheckoutTimeout( Integer.parseInt( checkoutTimeoutStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setCheckoutTimeout( defaults.getCheckoutTimeout() );
+	else
+	    pcfg.setCheckoutTimeout( C3P0Defaults.checkoutTimeout() );
+
+	// acquireIncrement
+	if ( acquireIncrementStr != null )
+	    pcfg.setAcquireIncrement( Integer.parseInt( acquireIncrementStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setAcquireIncrement( defaults.getAcquireIncrement() );
+	else
+	    pcfg.setAcquireIncrement( C3P0Defaults.acquireIncrement() );
+
+	// acquireRetryAttempts
+	if ( acquireRetryAttemptsStr != null )
+	    pcfg.setAcquireRetryAttempts( Integer.parseInt( acquireRetryAttemptsStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setAcquireRetryAttempts( defaults.getAcquireRetryAttempts() );
+	else
+	    pcfg.setAcquireRetryAttempts( C3P0Defaults.acquireRetryAttempts() );
+
+	// acquireRetryDelay
+	if ( acquireRetryDelayStr != null )
+	    pcfg.setAcquireRetryDelay( Integer.parseInt( acquireRetryDelayStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setAcquireRetryDelay( defaults.getAcquireRetryDelay() );
+	else
+	    pcfg.setAcquireRetryDelay( C3P0Defaults.acquireRetryDelay() );
+
+	// breakAfterAcquireFailure
+	if ( breakAfterAcquireFailureStr != null )
+	    pcfg.setBreakAfterAcquireFailure( Boolean.valueOf(breakAfterAcquireFailureStr.trim()).booleanValue() );
+	else if (defaults != null)
+	    pcfg.setBreakAfterAcquireFailure( defaults.isBreakAfterAcquireFailure() );
+	else
+	    pcfg.setBreakAfterAcquireFailure( C3P0Defaults.breakAfterAcquireFailure() );
+
+	// usesTraditionalReflectiveProxies
+	if ( usesTraditionalReflectiveProxiesStr != null )
+	    pcfg.setUsesTraditionalReflectiveProxies( Boolean.valueOf(usesTraditionalReflectiveProxiesStr.trim()).booleanValue() );
+	else if (defaults != null)
+	    pcfg.setUsesTraditionalReflectiveProxies( defaults.isUsesTraditionalReflectiveProxies() );
+	else
+	    pcfg.setUsesTraditionalReflectiveProxies( C3P0Defaults.usesTraditionalReflectiveProxies() );
+
+	// testConnectionOnCheckout
+	if ( testConnectionOnCheckoutStr != null )
+	    pcfg.setTestConnectionOnCheckout( Boolean.valueOf(testConnectionOnCheckoutStr.trim()).booleanValue() );
+	else if (defaults != null)
+	    pcfg.setTestConnectionOnCheckout( defaults.isTestConnectionOnCheckout() );
+	else
+	    pcfg.setTestConnectionOnCheckout( C3P0Defaults.testConnectionOnCheckout() );
+
+	// testConnectionOnCheckin
+	if ( testConnectionOnCheckinStr != null )
+	    pcfg.setTestConnectionOnCheckin( Boolean.valueOf(testConnectionOnCheckinStr.trim()).booleanValue() );
+	else if (defaults != null)
+	    pcfg.setTestConnectionOnCheckin( defaults.isTestConnectionOnCheckin() );
+	else
+	    pcfg.setTestConnectionOnCheckin( C3P0Defaults.testConnectionOnCheckin() );
+
+	// autoCommitOnClose
+	if ( autoCommitOnCloseStr != null )
+	    pcfg.setAutoCommitOnClose( Boolean.valueOf(autoCommitOnCloseStr.trim()).booleanValue() );
+	else if (defaults != null)
+	    pcfg.setAutoCommitOnClose( defaults.isAutoCommitOnClose() );
+	else
+	    pcfg.setAutoCommitOnClose( C3P0Defaults.autoCommitOnClose() );
+
+	// forceIgnoreUnresolvedTransactions
+	if ( forceIgnoreUnresolvedTransactionsStr != null )
+	    pcfg.setForceIgnoreUnresolvedTransactions( Boolean.valueOf( forceIgnoreUnresolvedTransactionsStr.trim() ).booleanValue() );
+	else if (defaults != null)
+	    pcfg.setForceIgnoreUnresolvedTransactions( defaults.isForceIgnoreUnresolvedTransactions() );
+	else
+	    pcfg.setForceIgnoreUnresolvedTransactions( C3P0Defaults.forceIgnoreUnresolvedTransactions() );
+
+	// connectionTesterClassName
+	if ( connectionTesterClassName != null )
+	    pcfg.setConnectionTesterClassName( connectionTesterClassName.trim() );
+	else if (defaults != null)
+	    pcfg.setConnectionTesterClassName( defaults.getConnectionTesterClassName() );
+	else
+	    pcfg.setConnectionTesterClassName( C3P0Defaults.connectionTesterClassName() );
+
+	// automaticTestTable
+	if ( automaticTestTable != null )
+	    pcfg.setAutomaticTestTable( automaticTestTable.trim() );
+	else if (defaults != null)
+	    pcfg.setAutomaticTestTable( defaults.getAutomaticTestTable() );
+	else
+	    pcfg.setAutomaticTestTable( C3P0Defaults.automaticTestTable() );
+
+	// numHelperThreads
+	if ( numHelperThreadsStr != null )
+	    pcfg.setNumHelperThreads( Integer.parseInt( numHelperThreadsStr.trim() ) );
+	else if (defaults != null)
+	    pcfg.setNumHelperThreads( defaults.getNumHelperThreads() );
+	else
+	    pcfg.setNumHelperThreads( C3P0Defaults.numHelperThreads() );
+
+	// preferredTestQuery
+	if ( preferredTestQuery != null )
+	    pcfg.setPreferredTestQuery( preferredTestQuery.trim() );
+	else if (defaults != null)
+	    pcfg.setPreferredTestQuery( defaults.getPreferredTestQuery() );
+	else
+	    pcfg.setPreferredTestQuery( C3P0Defaults.preferredTestQuery() );
+
+	// factoryClassLocation
+	if ( factoryClassLocation != null )
+	    pcfg.setFactoryClassLocation( factoryClassLocation.trim() );
+	else if (defaults != null)
+	    pcfg.setFactoryClassLocation( defaults.getFactoryClassLocation() );
+	else
+	    pcfg.setFactoryClassLocation( C3P0Defaults.factoryClassLocation() );
+    }
+
+    private static Properties findResourceProperties()
+    { return MultiPropertiesConfig.readVmConfig().getPropertiesByResourcePath(DEFAULT_CONFIG_RSRC_PATH); }
+
+    private static Properties origFindResourceProperties()
+    {
+ 	Properties props = new Properties();
+
+ 	InputStream is = null; 
+ 	try
+ 	    {
+ 		is = PoolConfig.class.getResourceAsStream(DEFAULT_CONFIG_RSRC_PATH);
+ 		if ( is != null )
+ 		    props.load( is );
+ 	    }
+ 	catch (IOException e)
+ 	    {
+ 		//e.printStackTrace();
+ 		if ( logger.isLoggable( MLevel.WARNING ) )
+ 		    logger.log( MLevel.WARNING, "An IOException occurred while trying to read Pool properties!", e );
+ 		props = new Properties(); 
+ 	    }
+ 	finally
+ 	    { InputStreamUtils.attemptClose( is ); }
+
+ 	return props;
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/PooledDataSource.java b/src/classes/com/mchange/v2/c3p0/PooledDataSource.java
new file mode 100644
index 0000000..7cc197a
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/PooledDataSource.java
@@ -0,0 +1,283 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.SQLException;
+import javax.sql.DataSource;
+import java.util.Collection;
+
+/**
+ *  <p><b>Most clients need never use or know about this interface -- c3p0 pooled DataSources
+ *  can be treated like any other DataSource.</b></p>
+ *
+ *  <p>The functionality in this interface will be only be of interest if 1) for administrative
+ *  reasons you like to keep close track of the number and status of all Connections your application
+ *  is using; 2) to work around problems encountered while managing a DataSource whose clients are
+ *  poorly coded applications that leak Connections, but which you are not permitted to fix; 
+ *  or 3) to work around problems that may occur if an underlying jdbc driver / DBMS system is
+ *  unreliable. In the third case, most users will be better off not using the present interface
+ *  at all, and using the DataSources' <tt>maxIdleTime</tt>, <tt>idleConnectionTestPeriod</tt>,
+ *  or <tt>testConnectionOnCheckout</tt> parameters to help your DataSources "automatically" heal. 
+ *  But for those who prefer a more direct, manual approach, this interface is for you. It is anticipated
+ *  that the methods of this interface will primarily be of use to administrators managing c3p0
+ *  PooledDataSources via JMX MBeans.</p>
+ *
+ *  <a name="peruserpools"><h3>Method Names & Per-User Pools</h3></a>
+ *
+ *  <p>To understand this interface, you need to realize that a c3p0 PooledDataSource may represent
+ *  not just one pool of Connections, but many, if users call the method
+ *  <tt>Connection getConnection(String username, String password)</tt> rather than the
+ *  no-argument <tt>getConnection()</tt> method. If users make use of non-default username, password
+ *  combinations, there will be a separate pool for each set of authentification criteria supplied.</p>
+ *
+ *  <p>Many methods in this interface have three variants:</p>
+ *  <ol>
+ *    <li><tt><i><method-name></i>DefaultUser()</tt></li>
+ *    <li><tt><i><method-name></i>(String username, String password)</tt></li>
+ *    <li><tt><i><method-name></i>AllUsers()</tt></li>
+ *  </ol>
+ *  <p>The first variant makes use of the pool maintained for the default user --
+ *  Connections created by calls to the no argument <tt>getConnection()</tt>,
+ *  the second variant lets you keeps track of pools created by calling 
+ *  <tt>getConnection( <i>username</i>, <i>password</i> )</tt>, and the third variant
+ *  provides aggregate information or performs operation on all pools.</p> 
+ *
+ *  <p>Under most circumstances, non-default authentication credentials will not
+ *  be used, and methods of the first variant are sufficient to manage the DataSource.</p> 
+ *
+ *  <h3>Soft and Hard Resets</h3>
+ *
+ *  <p>A properly configured PooledDataSource whose applications are careful to close all checked-out Connections
+ *  would never need to use these methods. But, sometimes applications are untrustworthy
+ *  and leak Connections, or database administrators suspect that Connections may be corrupt or invalid,
+ *  and would like to force a pool to flush and acquire fresh Connections. This interface provides two 
+ *  ways to do so.</p>
+ *
+ *  <ol>
+ *    <li><b><tt>hardReset()</tt></b> immediately closes all Connections managed by the DataSource, including
+ *    those that are currently checked out, bringing the DataSource back to the state it was in before
+ *    the first client called getConnection(). This method is obviously disruptive, and should be with
+ *    great care. Administrators who need to work around client applications that leak Connections, can
+ *    periodically poll for pool exhaustion (using the methods of this class, or by attempting to retrieve
+ *    a Connection and timing out) and use this method clean-up all Connections and start over. But calling
+ *    this method risks breaking Connections in current use by valid applications.<br/><br/></li>
+ *
+ *    <li><b><tt>softResetDefaultUser()</tt></b>, <b><tt>softReset( <i>username</i>, <i>password</i> )</tt></b> and
+ *    <b><tt>softResetAllUsers()</tt></b> asks the DataSource to flush its current pool of Connections and
+ *    reacquire <i>without</i> invalidating currently checked-out Connections. Currently checked out Connections
+ *    are logically removed from the pool, but their destruction is deferred until a client attempts to close() / check-in
+ *    the Connection. Administrators who suspect that some Connections in the pool may be invalid, but who do not
+ *    wish to rely upon c3p0's automatic testing and detection mechanisms to resolve the problem, may call these
+ *    methods to force a refresh without disrupting current clients. Administrators who suspect that clients may be
+ *    leaking Connections may minimize disruptive hardReset() calls by using softReset() until the number of unclosed
+ *    orphaned connections reaches an unacceptable level. (See <a href="#peruserpools">above</a> to understand
+ *    why there are three variants of this method.)</li> 
+ *  </ol>
+ *
+ *  <h3>Understanding Connection Counts</h3>
+ *
+ *  <p>For each <a href="#peruserpools">per-user pool</a>, four different statistics are available:</p>
+ *
+ *  <ol>
+ *    <li><tt>numConnections</tt> represents the total number of Connections in the pool.<br/><br/></li>
+ *    <li><tt>numIdleConnections</tt> represents the number of Connections in the pool that are currently available for checkout.<br/><br/></li> 
+ *    <li><tt>numBusyConnections</tt> represents the number of Connections in the pool that are currently checked out. The
+ *    invariant <tt>numIdleConnections + numBusyConnections == numConnections</tt> should always hold.<br/><br/></li>
+ *    <li><tt>numUnclosedOrphanedConnections</tt> will only be non-zero following a call to <tt>softReset()</tt>. It represents
+ *    the number of Connections that were checked out when a soft reset occurred and were therefore
+ *    silently excluded from the pool, and which remain unclosed by the client application.</li>
+ *  </ol>
+ */
+public interface PooledDataSource extends DataSource
+{
+    public String getIdentityToken();
+    public String getDataSourceName();
+    public void setDataSourceName(String dataSourceName);
+
+    /** @deprecated use getNumConnectionsDefaultUser() */
+    public int getNumConnections() throws SQLException;
+
+    /** @deprecated use getNumIdleConnectionsDefaultUser() */
+    public int getNumIdleConnections() throws SQLException;
+
+    /** @deprecated use getNumBusyConnectionsDefaultUser() */
+    public int getNumBusyConnections() throws SQLException;
+
+    /** @deprecated use getNumUnclosedOrphanedConnectionsDefaultUser() */
+    public int getNumUnclosedOrphanedConnections() throws SQLException;
+
+    public int getNumConnectionsDefaultUser() throws SQLException;
+    public int getNumIdleConnectionsDefaultUser() throws SQLException;
+    public int getNumBusyConnectionsDefaultUser() throws SQLException;
+    public int getNumUnclosedOrphanedConnectionsDefaultUser() throws SQLException;
+    public int getStatementCacheNumStatementsDefaultUser() throws SQLException;
+    public int getStatementCacheNumCheckedOutDefaultUser() throws SQLException;
+    public int getStatementCacheNumConnectionsWithCachedStatementsDefaultUser() throws SQLException;
+    public long getStartTimeMillisDefaultUser() throws SQLException;
+    public long getUpTimeMillisDefaultUser() throws SQLException;
+    public long getNumFailedCheckinsDefaultUser() throws SQLException;
+    public long getNumFailedCheckoutsDefaultUser() throws SQLException;
+    public long getNumFailedIdleTestsDefaultUser() throws SQLException;
+    public float getEffectivePropertyCycleDefaultUser() throws SQLException;
+    public int getNumThreadsAwaitingCheckoutDefaultUser() throws SQLException;
+
+    /**
+     * Discards all Connections managed by the PooledDataSource's default-authentication pool
+     * and reacquires new Connections to populate.
+     * Current checked out Connections will still
+     * be valid, and should still be checked into the
+     * PooledDataSource (so the PooledDataSource can destroy 
+     * them).
+     */
+    public void softResetDefaultUser() throws SQLException;
+
+    public int getNumConnections(String username, String password) throws SQLException;
+    public int getNumIdleConnections(String username, String password) throws SQLException;
+    public int getNumBusyConnections(String username, String password) throws SQLException;
+    public int getNumUnclosedOrphanedConnections(String username, String password) throws SQLException;
+    public int getStatementCacheNumStatements(String username, String password) throws SQLException;
+    public int getStatementCacheNumCheckedOut(String username, String password) throws SQLException;
+    public int getStatementCacheNumConnectionsWithCachedStatements(String username, String password) throws SQLException;
+    public float getEffectivePropertyCycle(String username, String password) throws SQLException;
+    public int getNumThreadsAwaitingCheckout(String username, String password) throws SQLException;
+
+    /**
+     * Discards all Connections managed by the PooledDataSource with the specified authentication credentials
+     * and reacquires new Connections to populate.
+     * Current checked out Connections will still
+     * be valid, and should still be checked into the
+     * PooledDataSource (so the PooledDataSource can destroy 
+     * them).
+     */
+    public void softReset(String username, String password) throws SQLException;
+
+    public int getNumBusyConnectionsAllUsers() throws SQLException;
+    public int getNumIdleConnectionsAllUsers() throws SQLException;
+    public int getNumConnectionsAllUsers() throws SQLException;
+    public int getNumUnclosedOrphanedConnectionsAllUsers() throws SQLException;
+
+    public int getStatementCacheNumStatementsAllUsers() throws SQLException;
+    public int getStatementCacheNumCheckedOutStatementsAllUsers() throws SQLException;
+    public int getStatementCacheNumConnectionsWithCachedStatementsAllUsers() throws SQLException;
+
+    public int getThreadPoolSize() throws SQLException;
+    public int getThreadPoolNumActiveThreads() throws SQLException;
+    public int getThreadPoolNumIdleThreads() throws SQLException;
+    public int getThreadPoolNumTasksPending() throws SQLException;
+
+    public String sampleThreadPoolStackTraces() throws SQLException;
+    public String sampleThreadPoolStatus() throws SQLException;
+
+    public String sampleStatementCacheStatusDefaultUser() throws SQLException;
+    public String sampleStatementCacheStatus(String username, String password) throws SQLException;
+    
+    public Throwable getLastAcquisitionFailureDefaultUser() throws SQLException;
+    public Throwable getLastCheckinFailureDefaultUser() throws SQLException;
+    public Throwable getLastCheckoutFailureDefaultUser() throws SQLException;
+    public Throwable getLastIdleTestFailureDefaultUser() throws SQLException;
+    public Throwable getLastConnectionTestFailureDefaultUser() throws SQLException;
+    
+    public Throwable getLastAcquisitionFailure(String username, String password) throws SQLException;
+    public Throwable getLastCheckinFailure(String username, String password) throws SQLException;
+    public Throwable getLastCheckoutFailure(String username, String password) throws SQLException;
+    public Throwable getLastIdleTestFailure(String username, String password) throws SQLException;
+    public Throwable getLastConnectionTestFailure(String username, String password) throws SQLException;
+    
+    public String sampleLastAcquisitionFailureStackTraceDefaultUser() throws SQLException;
+    public String sampleLastCheckinFailureStackTraceDefaultUser() throws SQLException;
+    public String sampleLastCheckoutFailureStackTraceDefaultUser() throws SQLException;
+    public String sampleLastIdleTestFailureStackTraceDefaultUser() throws SQLException;
+    public String sampleLastConnectionTestFailureStackTraceDefaultUser() throws SQLException;
+    
+    public String sampleLastAcquisitionFailureStackTrace(String username, String password) throws SQLException;
+    public String sampleLastCheckinFailureStackTrace(String username, String password) throws SQLException;
+    public String sampleLastCheckoutFailureStackTrace(String username, String password) throws SQLException;
+    public String sampleLastIdleTestFailureStackTrace(String username, String password) throws SQLException;
+    public String sampleLastConnectionTestFailureStackTrace(String username, String password) throws SQLException;
+
+    /**
+     * Discards all Connections managed by the PooledDataSource
+     * and reacquires new Connections to populate.
+     * Current checked out Connections will still
+     * be valid, and should still be checked into the
+     * PooledDataSource (so the PooledDataSource can destroy 
+     * them).
+     */
+    public void softResetAllUsers() throws SQLException;
+
+    public int getNumUserPools() throws SQLException;
+    public int getNumHelperThreads() throws SQLException;
+
+    public Collection getAllUsers() throws SQLException;
+
+    /**
+     * Destroys all pooled and checked-out Connections associated with
+     * this DataSource immediately. The PooledDataSource is
+     * reset to its initial state prior to first Connection acquisition,
+     * with no pools yet active, but ready for requests.  
+     */
+    public void hardReset() throws SQLException;
+
+    /**
+     * <p>C3P0 pooled DataSources use no resources before they are actually used in a VM,
+     * and they close themselves in their finalize() method. When they are active and
+     * pooling, they may have open database connections and their pool may spawn several threads
+     * for its maintenance. You can use this method to clean these resource methods up quickly
+     * when you will no longer be using this DataSource. The resources will actually be cleaned up only if 
+     * no other DataSources are sharing the same pool.</p>
+     *
+     * <p>You can equivalently use the static method destroy() in the DataSources class to clean-up
+     * these resources.</p>
+     *
+     * <p>This is equivalent to calling close( false ).</p>
+     *
+     * @see DataSources#destroy
+     */
+    public void close() throws SQLException;
+
+    /**
+     * <p>Should be used only with great caution. If <tt>force_destroy</tt> is set to true,
+     *    this immediately destroys any pool and cleans up all resources
+     *    this DataSource may be using, <u><i>even if other DataSources are sharing that
+     *    pool!</i></u> In general, it is difficult to know whether a pool is being shared by
+     *    multiple DataSources. It may depend upon whether or not a JNDI implementation returns
+     *    a single instance or multiple copies upon lookup (which is undefined by the JNDI spec).</p>
+     *
+     * <p>In general, this method should be used only when you wish to wind down all c3p0 pools
+     *    in a ClassLoader. For example, when shutting down and restarting a web application
+     *    that uses c3p0, you may wish to kill all threads making use of classes loaded by a 
+     *    web-app specific ClassLoader, so that the ClassLoader can be cleanly garbage collected.
+     *    In this case, you may wish to use force destroy. Otherwise, it is much safer to use
+     *    the simple destroy() method, which will not shut down pools that may still be in use.</p>
+     *
+     * <p><b>To close a pool normally, use the no argument close method, or set <tt>force_destroy</tt>
+     *    to false.</b></p>
+     *    
+     *  @deprecated the force_destroy argument is now meaningless, as pools are no longer
+     *              potentially shared between multiple DataSources.
+     *
+     *  @see #close()
+     */
+    public void close(boolean force_destory) throws SQLException;
+}
diff --git a/src/classes/com/mchange/v2/c3p0/QueryConnectionTester.java b/src/classes/com/mchange/v2/c3p0/QueryConnectionTester.java
new file mode 100644
index 0000000..293307d
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/QueryConnectionTester.java
@@ -0,0 +1,32 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.Connection;
+
+public interface QueryConnectionTester extends ConnectionTester
+{
+    public int activeCheckConnection(Connection c, String preferredTestQuery);
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/SQLWarnings.java b/src/classes/com/mchange/v2/c3p0/SQLWarnings.java
new file mode 100644
index 0000000..ad1a3c0
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/SQLWarnings.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+
+public final class SQLWarnings
+{
+    final static MLogger logger = MLog.getLogger( SQLWarnings.class );
+
+    public static void logAndClearWarnings(Connection con) throws SQLException
+    {
+        if (logger.isLoggable(MLevel.INFO))
+        {
+            for(SQLWarning w = con.getWarnings(); w != null; w = w.getNextWarning())
+                logger.log(MLevel.INFO, w.getMessage(), w);
+        }
+        con.clearWarnings();
+    }
+
+}
diff --git a/src/classes/com/mchange/v2/c3p0/UnifiedConnectionTester.java b/src/classes/com/mchange/v2/c3p0/UnifiedConnectionTester.java
new file mode 100644
index 0000000..6b4bc9e
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/UnifiedConnectionTester.java
@@ -0,0 +1,69 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.sql.Connection;
+
+/**
+ *  <p>Having expanded the once-simple ConnectionTester interface to support both
+ *  user-specified queries and return of root cause Exceptions (via an out-param),
+ *  this interface has grown unnecessarily complex.</p>
+ *  
+ *  <p>If you wish to implement a custom Connection tester, here is the simple
+ *  way to do it</p>
+ *  
+ *  <ol>
+ *    <li>Extend {@link com.mchange.v2.c3p0.AbstractConnectionTester}</li>
+ *    <li>Override only the two abstract methods</li>
+ *    <ul>
+ *       <li><tt>public int activeCheckConnection(Connection c, String preferredTestQuery, Throwable[] rootCauseOutParamHolder)</tt></li>
+ *       <li><tt>public int statusOnException(Connection c, Throwable t, String preferredTestQuery, Throwable[] rootCauseOutParamHolder)</tt></li>
+ *    </ul>
+ *    <li>Take care to ensure that your methods are defined to allow <tt>preferredTestQuery</tt> and 
+ *    <tt>rootCauseOutParamHolder</tt> to be <tt>null</tt>.</li>
+ *  </ol>
+ *  
+ *  <p>Parameter <tt>rootCauseOutParamHolder</tt> is an optional parameter, which if supplied, will be a Throwable array whose size
+ *  it at least one. If a Connection test fails because of some Exception, the Connection tester may set this Exception as the
+ *  zero-th element of the array to provide information about why and how the test failed.</p> 
+ */
+public interface UnifiedConnectionTester extends FullQueryConnectionTester
+{
+    public final static int CONNECTION_IS_OKAY       = ConnectionTester.CONNECTION_IS_OKAY;
+    public final static int CONNECTION_IS_INVALID    = ConnectionTester.CONNECTION_IS_INVALID;
+    public final static int DATABASE_IS_INVALID      = ConnectionTester.DATABASE_IS_INVALID;
+    
+    public int activeCheckConnection(Connection c);
+    public int activeCheckConnection(Connection c, Throwable[] rootCauseOutParamHolder);
+    public int activeCheckConnection(Connection c, String preferredTestQuery);
+    public int activeCheckConnection(Connection c, String preferredTestQuery, Throwable[] rootCauseOutParamHolder);
+
+    public int statusOnException(Connection c, Throwable t);
+    public int statusOnException(Connection c, Throwable t, Throwable[] rootCauseOutParamHolder);
+    public int statusOnException(Connection c, Throwable t, String preferredTestQuery);
+    public int statusOnException(Connection c, Throwable t, String preferredTestQuery, Throwable[] rootCauseOutParamHolder);
+
+    public boolean equals(Object o);
+    public int hashCode();
+}
diff --git a/src/classes/com/mchange/v2/c3p0/WrapperConnectionPoolDataSource.java b/src/classes/com/mchange/v2/c3p0/WrapperConnectionPoolDataSource.java
new file mode 100644
index 0000000..a24a8a4
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/WrapperConnectionPoolDataSource.java
@@ -0,0 +1,286 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+import java.beans.PropertyChangeListener;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.cfg.C3P0Config;
+import com.mchange.v2.c3p0.impl.*;
+import com.mchange.v2.log.*;
+
+// MT: Most methods are left unsynchronized, because getNestedDataSource() is synchronized, and for most methods, that's
+//     the only critical part. Previous oversynchronization led to hangs, when getting the Connection for one Thread happened
+//     to hang, blocking access to getPooledConnection() for all Threads.
+public final class WrapperConnectionPoolDataSource extends WrapperConnectionPoolDataSourceBase implements ConnectionPoolDataSource
+{
+    final static MLogger logger = MLog.getLogger( WrapperConnectionPoolDataSource.class );
+
+    //MT: protected by this' lock
+    ConnectionTester connectionTester = C3P0ImplUtils.defaultConnectionTester();
+    Map              userOverrides;
+
+    public WrapperConnectionPoolDataSource(boolean autoregister)
+    {
+	super( autoregister );
+
+	setUpPropertyListeners();
+
+	//set up initial value of userOverrides
+	try
+	    { this.userOverrides = C3P0ImplUtils.parseUserOverridesAsString( this.getUserOverridesAsString() ); }
+	catch (Exception e)
+	    {
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "Failed to parse stringified userOverrides. " + this.getUserOverridesAsString(), e );
+	    }
+    }
+
+    public WrapperConnectionPoolDataSource()
+    { this( true ); }
+
+    private void setUpPropertyListeners()
+    {
+	VetoableChangeListener setConnectionTesterListener = new VetoableChangeListener()
+	    {
+		// always called within synchronized mutators of the parent class... needn't explicitly sync here
+		public void vetoableChange( PropertyChangeEvent evt ) throws PropertyVetoException
+		{
+		    String propName = evt.getPropertyName();
+		    Object val = evt.getNewValue();
+
+		    if ( "connectionTesterClassName".equals( propName ) )
+			{
+			    try
+				{ recreateConnectionTester( (String) val ); }
+			    catch ( Exception e )
+				{
+				    //e.printStackTrace();
+				    if ( logger.isLoggable( MLevel.WARNING ) )
+					logger.log( MLevel.WARNING, "Failed to create ConnectionTester of class " + val, e );
+				    
+				    throw new PropertyVetoException("Could not instantiate connection tester class with name '" + val + "'.", evt);
+				}
+			}
+		    else if ("userOverridesAsString".equals( propName ))
+			{
+			    try
+				{ WrapperConnectionPoolDataSource.this.userOverrides = C3P0ImplUtils.parseUserOverridesAsString( (String) val ); }
+			    catch (Exception e)
+				{
+				    if ( logger.isLoggable( MLevel.WARNING ) )
+					logger.log( MLevel.WARNING, "Failed to parse stringified userOverrides. " + val, e );
+				    
+				    throw new PropertyVetoException("Failed to parse stringified userOverrides. " + val, evt);
+				}
+			}
+		}
+	    };
+	this.addVetoableChangeListener( setConnectionTesterListener );
+    }
+
+    public WrapperConnectionPoolDataSource( String configName )
+    {
+	this();
+	
+	try
+	    {
+		if (configName != null)
+		    C3P0Config.bindNamedConfigToBean( this, configName ); 
+	    }
+	catch (Exception e)
+	    {
+		if (logger.isLoggable( MLevel.WARNING ))
+		    logger.log( MLevel.WARNING, 
+				"Error binding WrapperConnectionPoolDataSource to named-config '" + configName + 
+				"'. Some default-config values may be used.", 
+				e);
+	    }
+    }
+
+    // implementation of javax.sql.ConnectionPoolDataSource
+
+    public PooledConnection getPooledConnection()
+	throws SQLException
+    { return this.getPooledConnection( (ConnectionCustomizer) null, null ); }
+
+    // getNestedDataSource() is sync'ed, which is enough. Unsync'ed this method,
+    // because when sync'ed a hang in retrieving one connection blocks all
+    //
+    protected PooledConnection getPooledConnection( ConnectionCustomizer cc, String pdsIdt )
+	throws SQLException
+    { 
+	DataSource nds = getNestedDataSource();
+	if (nds == null)
+	    throw new SQLException( "No standard DataSource has been set beneath this wrapper! [ nestedDataSource == null ]");
+	Connection conn = nds.getConnection();
+	if (conn == null)
+	    throw new SQLException("An (unpooled) DataSource returned null from its getConnection() method! " +
+				   "DataSource: " + getNestedDataSource());
+	if ( this.isUsesTraditionalReflectiveProxies() )
+	    {
+		//return new C3P0PooledConnection( new com.mchange.v2.c3p0.test.CloseReportingConnection( conn ), 
+		return new C3P0PooledConnection( conn, 
+						 connectionTester,
+						 this.isAutoCommitOnClose(), 
+						 this.isForceIgnoreUnresolvedTransactions(),
+						 cc,
+						 pdsIdt); 
+	    }
+	else
+	    {
+		return new NewPooledConnection( conn, 
+						connectionTester,
+						this.isAutoCommitOnClose(), 
+						this.isForceIgnoreUnresolvedTransactions(),
+						this.getPreferredTestQuery(),
+						cc,
+						pdsIdt); 
+	    }
+    } 
+ 
+    public PooledConnection getPooledConnection(String user, String password)
+	throws SQLException
+    { return this.getPooledConnection( user, password, null, null ); }
+
+    // getNestedDataSource() is sync'ed, which is enough. Unsync'ed this method,
+    // because when sync'ed a hang in retrieving one connection blocks all
+    //
+    protected PooledConnection getPooledConnection(String user, String password, ConnectionCustomizer cc, String pdsIdt)
+	throws SQLException
+    { 
+	DataSource nds = getNestedDataSource();
+	if (nds == null)
+	    throw new SQLException( "No standard DataSource has been set beneath this wrapper! [ nestedDataSource == null ]");
+	Connection conn = nds.getConnection(user, password);
+	if (conn == null)
+	    throw new SQLException("An (unpooled) DataSource returned null from its getConnection() method! " +
+				   "DataSource: " + getNestedDataSource());
+	if ( this.isUsesTraditionalReflectiveProxies() )
+	    {
+		//return new C3P0PooledConnection( new com.mchange.v2.c3p0.test.CloseReportingConnection( conn ), 
+		return new C3P0PooledConnection( conn,
+						 connectionTester,
+						 this.isAutoCommitOnClose(), 
+						 this.isForceIgnoreUnresolvedTransactions(),
+						 cc,
+						 pdsIdt);
+	    }
+	else
+	    {
+		return new NewPooledConnection( conn, 
+						connectionTester,
+						this.isAutoCommitOnClose(), 
+						this.isForceIgnoreUnresolvedTransactions(),
+						this.getPreferredTestQuery(),
+						cc,
+						pdsIdt); 
+	    }
+    }
+ 
+    public PrintWriter getLogWriter()
+	throws SQLException
+    { return getNestedDataSource().getLogWriter(); }
+
+    public void setLogWriter(PrintWriter out)
+	throws SQLException
+    { getNestedDataSource().setLogWriter( out ); }
+
+    public void setLoginTimeout(int seconds)
+	throws SQLException
+    { getNestedDataSource().setLoginTimeout( seconds ); }
+
+    public int getLoginTimeout()
+	throws SQLException
+    { return getNestedDataSource().getLoginTimeout(); }
+
+    //"virtual properties"
+
+    public String getUser()
+    { 
+	try { return C3P0ImplUtils.findAuth( this.getNestedDataSource() ).getUser(); }
+	catch (SQLException e)
+	    {
+		//e.printStackTrace();
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, 
+				"An Exception occurred while trying to find the 'user' property from our nested DataSource." +
+				" Defaulting to no specified username.", e );
+		return null; 
+	    }
+    }
+
+    public String getPassword()
+    { 
+	try { return C3P0ImplUtils.findAuth( this.getNestedDataSource() ).getPassword(); }
+	catch (SQLException e)
+	    { 
+		//e.printStackTrace();
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "An Exception occurred while trying to find the 'password' property from our nested DataSource." + 
+				" Defaulting to no specified password.", e );
+		return null; 
+	    }
+    }
+
+    public Map getUserOverrides()
+    { return userOverrides; }
+
+    public String toString()
+    {
+	StringBuffer sb = new StringBuffer();
+	sb.append( super.toString() );
+
+// 	if (userOverrides != null)
+// 	    sb.append("; userOverrides: " + userOverrides.toString());
+
+	return sb.toString();
+    }
+
+    protected String extraToStringInfo()
+    {
+	if (userOverrides != null)
+	    return "; userOverrides: " + userOverrides.toString();
+	else
+	    return null;
+    }
+
+    //other code
+    private synchronized void recreateConnectionTester(String className) throws Exception
+    {
+	if (className != null)
+	    {
+		ConnectionTester ct = (ConnectionTester) Class.forName( className ).newInstance();
+		this.connectionTester = ct;
+	    }
+	else
+	    this.connectionTester = C3P0ImplUtils.defaultConnectionTester();
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/cfg/C3P0Config.java b/src/classes/com/mchange/v2/c3p0/cfg/C3P0Config.java
new file mode 100644
index 0000000..9144023
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/cfg/C3P0Config.java
@@ -0,0 +1,329 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.cfg;
+
+import java.beans.*;
+import java.util.*;
+import com.mchange.v2.c3p0.impl.*;
+import com.mchange.v2.beans.*;
+import com.mchange.v2.cfg.*;
+import com.mchange.v2.log.*;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import com.mchange.v1.lang.BooleanUtils;
+
+//all internal maps should be HashMaps (the implementation presumes HashMaps)
+
+public final class C3P0Config
+{
+    public final static String CFG_FINDER_CLASSNAME_KEY = "com.mchange.v2.c3p0.cfg.finder";
+
+    public final static String DEFAULT_CONFIG_NAME = "default";
+
+    public final static C3P0Config MAIN;
+
+    final static MLogger logger = MLog.getLogger( C3P0Config.class );
+
+    static
+    {
+// 	Set knownProps = new HashSet();
+// 	knownProps.add("acquireIncrement");
+// 	knownProps.add("acquireRetryAttempts");
+// 	knownProps.add("acquireRetryDelay");
+// 	knownProps.add("autoCommitOnClose");
+// 	knownProps.add("automaticTestTable");
+// 	knownProps.add("breakAfterAcqireFailure");
+// 	knownProps.add("checkoutTimeout");
+// 	knownProps.add("connectionTesterClassName");
+// 	knownProps.add("factoryClassLocation");
+// 	knownProps.add("forceIgnoreUnresolvedTransactions");
+// 	knownProps.add("idleConnectionTestPeriod");
+// 	knownProps.add("initialPoolSize");
+// 	knownProps.add("maxIdleTime");
+// 	knownProps.add("maxPoolSize");
+
+	C3P0Config protoMain;
+
+	String cname = MultiPropertiesConfig.readVmConfig().getProperty( CFG_FINDER_CLASSNAME_KEY );
+
+	C3P0ConfigFinder cfgFinder = null;
+	try
+	    {
+		if (cname != null)
+		    cfgFinder = (C3P0ConfigFinder) Class.forName( cname ).newInstance();
+		
+	    }
+	catch (Exception e)
+	    {
+		if ( logger.isLoggable(MLevel.WARNING) )
+		    logger.log( MLevel.WARNING, "Could not load specified C3P0ConfigFinder class'" + cname + "'.", e);
+	    }
+
+	try
+	    { 
+		if (cfgFinder == null)
+		    {
+			Class.forName("org.w3c.dom.Node");
+			Class.forName("com.mchange.v2.c3p0.cfg.C3P0ConfigXmlUtils"); //fail nicely if we don't have XML libs
+			cfgFinder = new DefaultC3P0ConfigFinder();
+		    }
+		protoMain = cfgFinder.findConfig(); 
+	    }
+	catch (Exception e)
+	    { 
+		
+		if ( logger.isLoggable(MLevel.WARNING) )
+		    logger.log( MLevel.WARNING, "XML configuration disabled! Verify that standard XML libs are available.", e);
+
+		HashMap flatDefaults = C3P0ConfigUtils.extractHardcodedC3P0Defaults();
+		flatDefaults.putAll( C3P0ConfigUtils.extractC3P0PropertiesResources() );
+		protoMain = C3P0ConfigUtils.configFromFlatDefaults( flatDefaults );
+	    }
+	MAIN = protoMain;
+
+	warnOnUnknownProperties( MAIN );
+    }
+
+    private static void warnOnUnknownProperties( C3P0Config cfg )
+    {
+	warnOnUnknownProperties( cfg.defaultConfig );
+	for (Iterator ii = cfg.configNamesToNamedScopes.values().iterator(); ii.hasNext(); )
+	    warnOnUnknownProperties( (NamedScope) ii.next() );
+    }
+
+    private static void warnOnUnknownProperties( NamedScope scope )
+    {
+	warnOnUnknownProperties( scope.props );
+	for (Iterator ii = scope.userNamesToOverrides.values().iterator(); ii.hasNext(); )
+	    warnOnUnknownProperties( (Map) ii.next() );
+    }
+
+    private static void warnOnUnknownProperties( Map propMap )
+    {
+	for (Iterator ii = propMap.keySet().iterator(); ii.hasNext(); )
+	    {
+		String prop = (String) ii.next();
+		if (! C3P0Defaults.isKnownProperty( prop ) && logger.isLoggable( MLevel.WARNING ))
+		    logger.log( MLevel.WARNING, "Unknown c3p0-config property: " + prop);
+	    }
+    }
+
+    public static String getUnspecifiedUserProperty( String propKey, String configName )
+    {
+	  String out = null;
+
+ 	  if (configName == null)
+	      out = (String) MAIN.defaultConfig.props.get( propKey );
+	  else
+	      {
+		  NamedScope named = (NamedScope) MAIN.configNamesToNamedScopes.get( configName );
+		  if (named != null)
+		      out = (String) named.props.get(propKey);
+		  else
+		      logger.warning("named-config with name '" + configName + "' does not exist. Using default-config for property '" + propKey + "'.");
+
+		  if (out == null)
+		      out = (String) MAIN.defaultConfig.props.get( propKey );
+	      }
+	  
+	  return out;
+    }
+
+    public static Map getUnspecifiedUserProperties(String configName)
+    {
+	Map out = new HashMap();
+
+	out.putAll( MAIN.defaultConfig.props );
+
+	if (configName != null)
+	    {
+		  NamedScope named = (NamedScope) MAIN.configNamesToNamedScopes.get( configName );
+		  if (named != null)
+		      out.putAll( named.props );
+		  else
+		      logger.warning("named-config with name '" + configName + "' does not exist. Using default-config.");
+	    }
+
+	return out;
+    }
+
+    public static Map getUserOverrides( String configName )
+    {
+	Map out = new HashMap();
+
+	NamedScope namedConfigScope = null;
+
+	if (configName != null)
+	    namedConfigScope = (NamedScope) MAIN.configNamesToNamedScopes.get( configName );
+
+	out.putAll( MAIN.defaultConfig.userNamesToOverrides );
+
+	if (namedConfigScope != null)
+	    out.putAll( namedConfigScope.userNamesToOverrides );
+
+	return (out.isEmpty() ? null : out );
+    }
+
+    public static String getUserOverridesAsString(String configName) throws IOException
+    {
+	Map userOverrides = getUserOverrides( configName );
+	if (userOverrides == null)
+	    return null;
+	else
+	    return C3P0ImplUtils.createUserOverridesAsString( userOverrides ).intern();
+    }
+
+    final static Class[] SUOAS_ARGS = new Class[] { String.class };
+
+    final static Collection SKIP_BIND_PROPS = Arrays.asList( new String[] {"loginTimeout", "properties"} );
+
+    public static void bindNamedConfigToBean(Object bean, String configName) throws IntrospectionException
+    {
+	Map defaultUserProps = C3P0Config.getUnspecifiedUserProperties( configName );
+	BeansUtils.overwriteAccessiblePropertiesFromMap( defaultUserProps, 
+							 bean, 
+							 false, 
+							 SKIP_BIND_PROPS,
+							 true,
+							 MLevel.FINEST,
+							 MLevel.WARNING,
+							 false);
+	try
+	    {
+		Method m = bean.getClass().getMethod( "setUserOverridesAsString", SUOAS_ARGS );
+		m.invoke( bean, new Object[] {getUserOverridesAsString( configName )} );
+	    }
+	catch (NoSuchMethodException e)
+	    {
+		e.printStackTrace();
+		/* ignore */ 
+	    }
+	catch (Exception e)
+	    {
+		if (logger.isLoggable( MLevel.WARNING ))
+		    logger.log( MLevel.WARNING, 
+				"An exception occurred while trying to bind user overrides " +
+				"for named config '" + configName + "'. Only default user configs " +
+				"will be used."
+				, e);
+	    }
+    }
+
+    /*
+     *  Note that on initialization of a DataSource, no config name is known.
+     *  We initialize local vars using the default config. The DataSources class
+     *  and/or constructors that accept a configName then overwrite the initial
+     *  values with namedConfig overrides if supplied.
+     */
+    public static String initializeUserOverridesAsString()
+    {
+	try
+	    { return getUserOverridesAsString( null ); }
+	catch (Exception e)
+	    {
+		if (logger.isLoggable( MLevel.WARNING ))
+		    logger.log( MLevel.WARNING, "Error initializing default user overrides. User overrides may be ignored.", e);
+		return null;
+	    }
+    }
+
+    public static String initializeStringPropertyVar(String propKey, String dflt)
+    {
+	String out = getUnspecifiedUserProperty( propKey, null );
+	if (out == null) out = dflt;
+	return out;
+    }
+
+    public static int initializeIntPropertyVar(String propKey, int dflt)
+    {
+	boolean set = false;
+	int out = -1;
+
+	String outStr = getUnspecifiedUserProperty( propKey, null );
+	if (outStr != null)
+	    {
+		try 
+		    { 
+			out = Integer.parseInt( outStr.trim() ); 
+			set = true;
+		    }
+		catch (NumberFormatException e)
+		    {
+			logger.info("'" + outStr + "' is not a legal value for property '" + propKey +
+				    "'. Using default value: " + dflt);
+		    }
+	    }
+
+	if (!set)
+	    out = dflt;
+
+	//System.err.println("initializing " + propKey + " to " + out);
+	return out;
+    }
+
+    public static boolean initializeBooleanPropertyVar(String propKey, boolean dflt)
+    {
+	boolean set = false;
+	boolean out = false;
+
+	String outStr = getUnspecifiedUserProperty( propKey, null );
+	if (outStr != null)
+	    {
+		try 
+		    { 
+			out = BooleanUtils.parseBoolean( outStr.trim() ); 
+			set = true;
+		    }
+		catch (IllegalArgumentException e)
+		    {
+			logger.info("'" + outStr + "' is not a legal value for property '" + propKey +
+				    "'. Using default value: " + dflt);
+		    }
+	    }
+
+	if (!set)
+	    out = dflt;
+
+	return out;
+    }
+
+
+
+    NamedScope defaultConfig;
+    HashMap configNamesToNamedScopes;
+
+    C3P0Config( NamedScope defaultConfig, HashMap configNamesToNamedScopes)
+    {
+	this.defaultConfig = defaultConfig;
+	this.configNamesToNamedScopes = configNamesToNamedScopes;
+    }
+
+//     C3P0Config()
+//     {
+// 	this.defaultConfig = new NamedScope();
+// 	this.configNamesToNamedScopes = new HashMap();
+//     }
+
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/cfg/C3P0ConfigFinder.java b/src/classes/com/mchange/v2/c3p0/cfg/C3P0ConfigFinder.java
new file mode 100644
index 0000000..3e6efa1
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/cfg/C3P0ConfigFinder.java
@@ -0,0 +1,31 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.cfg;
+
+import java.sql.SQLException;
+
+public interface C3P0ConfigFinder
+{
+    public C3P0Config findConfig() throws Exception;
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/cfg/C3P0ConfigUtils.java b/src/classes/com/mchange/v2/c3p0/cfg/C3P0ConfigUtils.java
new file mode 100644
index 0000000..8a856a7
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/cfg/C3P0ConfigUtils.java
@@ -0,0 +1,160 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.cfg;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import com.mchange.v2.cfg.*;
+import com.mchange.v2.log.*;
+import com.mchange.v2.c3p0.impl.*;
+
+public final class C3P0ConfigUtils
+{
+    public final static String PROPS_FILE_RSRC_PATH     = "/c3p0.properties";
+    public final static String PROPS_FILE_PROP_PFX      = "c3p0.";
+    public final static int    PROPS_FILE_PROP_PFX_LEN  = 5;
+
+    private final static String[] MISSPELL_PFXS = {"/c3pO", "/c3po", "/C3P0", "/C3PO"}; 
+    
+    final static MLogger logger = MLog.getLogger( C3P0ConfigUtils.class );
+    
+    static
+    {
+        if ( logger.isLoggable(MLevel.WARNING) && C3P0ConfigUtils.class.getResource( PROPS_FILE_RSRC_PATH ) == null )
+        {
+            // warn on a misspelling... its an ugly way to do this, but since resources are not listable...
+            for (int i = 0; i < MISSPELL_PFXS.length; ++i)
+            {
+                String test = MISSPELL_PFXS[i] + ".properties";
+                if (C3P0ConfigUtils.class.getResource( MISSPELL_PFXS[i] + ".properties" ) != null)
+                {
+                    logger.warning("POSSIBLY MISSPELLED c3p0.properties CONFIG RESOURCE FOUND. " +
+                                   "Please ensure the file name is c3p0.properties, all lower case, " +
+                                   "with the digit 0 (NOT the letter O) in c3p0. It should be placed " +
+                                   " in the top level of c3p0's effective classpath.");
+                    break;
+                }
+            }
+        }
+    }
+
+    public static HashMap extractHardcodedC3P0Defaults(boolean stringify)
+    {
+	HashMap out = new HashMap();
+
+	try
+	    {
+		Method[] methods = C3P0Defaults.class.getMethods();
+		for (int i = 0, len = methods.length; i < len; ++i)
+		    {
+			Method m = methods[i];
+			int mods = m.getModifiers();
+			if ((mods & Modifier.PUBLIC) != 0 && (mods & Modifier.STATIC) != 0 && m.getParameterTypes().length == 0)
+			    {
+				if (stringify)
+				    {
+					Object val = m.invoke( null, null );
+					if ( val != null )
+					    out.put( m.getName(), String.valueOf( val ) );
+				    }
+				else
+				    out.put( m.getName(), m.invoke( null, null ) );
+			    }
+		    }
+	    }
+	catch (Exception e)
+	    {
+		logger.log( MLevel.WARNING, "Failed to extract hardcoded default config!?", e );
+	    }
+
+	return out;
+    }
+
+    public static HashMap extractHardcodedC3P0Defaults()
+    { return extractHardcodedC3P0Defaults( true ); }
+
+    public static HashMap extractC3P0PropertiesResources()
+    {
+	HashMap out = new HashMap();
+
+// 	Properties props = findResourceProperties();
+// 	props.putAll( findAllC3P0Properties() );
+
+ 	Properties props = findAllC3P0Properties();
+	for (Iterator ii = props.keySet().iterator(); ii.hasNext(); )
+	    {
+		String key = (String) ii.next();
+		String val = (String) props.get(key);
+		if ( key.startsWith(PROPS_FILE_PROP_PFX) )
+		    out.put( key.substring(PROPS_FILE_PROP_PFX_LEN).trim(), val.trim() );
+	    }
+
+	return out;
+    }
+
+    public static C3P0Config configFromFlatDefaults(HashMap flatDefaults)
+    {
+	NamedScope defaults = new NamedScope();
+	defaults.props.putAll( flatDefaults );
+	
+	HashMap configNamesToNamedScopes = new HashMap();
+	
+	return new C3P0Config( defaults, configNamesToNamedScopes ); 
+    }
+    
+    public static String getPropFileConfigProperty( String prop )
+    { return MultiPropertiesConfig.readVmConfig().getProperty( prop ); }
+
+    private static Properties findResourceProperties()
+    { return MultiPropertiesConfig.readVmConfig().getPropertiesByResourcePath(PROPS_FILE_RSRC_PATH); }
+
+    private static Properties findAllC3P0Properties()
+    { return MultiPropertiesConfig.readVmConfig().getPropertiesByPrefix("c3p0"); }
+
+    static Properties findAllC3P0SystemProperties()
+    {
+	Properties out = new Properties();
+
+	SecurityException sampleExc = null;
+	try
+	    {
+		for (Iterator ii = C3P0Defaults.getKnownProperties().iterator(); ii.hasNext(); )
+		    {
+			String key = (String) ii.next();
+			String prefixedKey = "c3p0." + key;
+			String value = System.getProperty( prefixedKey );
+			if (value != null && value.trim().length() > 0)
+			    out.put( key, value );
+		    }
+	    }
+	catch (SecurityException e)
+	    { sampleExc = e; }
+
+	return out;
+    }
+
+    private C3P0ConfigUtils()
+    {}
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/cfg/C3P0ConfigXmlUtils.java b/src/classes/com/mchange/v2/c3p0/cfg/C3P0ConfigXmlUtils.java
new file mode 100644
index 0000000..3878e89
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/cfg/C3P0ConfigXmlUtils.java
@@ -0,0 +1,232 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.cfg;
+
+import java.io.*;
+import java.util.*;
+import javax.xml.parsers.*;
+import org.w3c.dom.*;
+import com.mchange.v2.log.*;
+
+import com.mchange.v1.xml.DomParseUtils;
+
+public final class C3P0ConfigXmlUtils
+{
+    public final static String XML_CONFIG_RSRC_PATH     = "/c3p0-config.xml";
+
+    final static MLogger logger = MLog.getLogger( C3P0ConfigXmlUtils.class );
+
+    public final static String LINESEP;
+
+    private final static String[] MISSPELL_PFXS = {"/c3p0", "/c3pO", "/c3po", "/C3P0", "/C3PO"}; 
+    private final static char[]   MISSPELL_LINES = {'-', '_'};
+    private final static String[] MISSPELL_CONFIG = {"config", "CONFIG"};
+    private final static String[] MISSPELL_XML = {"xml", "XML"};
+
+    // its an ugly way to do this, but since resources are not listable...
+    //
+    // this is only executed once, and does about 40 tests (for now)
+    // should I care about the cost in initialization time?
+    //
+    // should only be run if we've checked for the correct file, but
+    // not found it
+    private final static void warnCommonXmlConfigResourceMisspellings()
+    {
+        if (logger.isLoggable( MLevel.WARNING) )
+        {
+            for (int a = 0, lena = MISSPELL_PFXS.length; a < lena; ++a)
+            {
+                StringBuffer sb = new StringBuffer(16);
+                sb.append( MISSPELL_PFXS[a] );
+                for (int b = 0, lenb = MISSPELL_LINES.length; b < lenb; ++b)
+                {
+                    sb.append(MISSPELL_LINES[b]);
+                    for (int c = 0, lenc = MISSPELL_CONFIG.length; c < lenc; ++c)
+                    {
+                        sb.append(MISSPELL_CONFIG[c]);
+                        sb.append('.');
+                        for (int d = 0, lend = MISSPELL_XML.length; d < lend; ++d)
+                        {
+                            sb.append(MISSPELL_XML[d]);
+                            String test = sb.toString();
+                            if (!test.equals(XML_CONFIG_RSRC_PATH))
+                            {
+                                Object hopefullyNull = C3P0ConfigXmlUtils.class.getResource( test );
+                                if (hopefullyNull != null)
+                                {
+                                    logger.warning("POSSIBLY MISSPELLED c3p0-conf.xml RESOURCE FOUND. " +
+                                                   "Please ensure the file name is c3p0-config.xml, all lower case, " +
+                                                   "with the digit 0 (NOT the letter O) in c3p0. It should be placed " +
+                                                   " in the top level of c3p0's effective classpath.");
+                                    return;
+                                }
+                            }
+                        }
+                    }
+
+                }
+            }
+        }
+    }
+
+    static
+    {
+        String ls;
+
+        try
+        { ls = System.getProperty("line.separator", "\r\n"); }
+        catch (Exception e)
+        { ls = "\r\n"; }
+
+        LINESEP = ls;
+
+    }
+
+    public static C3P0Config extractXmlConfigFromDefaultResource() throws Exception
+    {
+        InputStream is = null;
+
+        try
+        {
+            is = C3P0ConfigUtils.class.getResourceAsStream(XML_CONFIG_RSRC_PATH);
+            if ( is == null )
+            {
+                warnCommonXmlConfigResourceMisspellings();
+                return null;
+            }
+            else
+                return extractXmlConfigFromInputStream( is );
+        }
+        finally
+        {
+            try { if (is != null) is.close(); }
+            catch (Exception e)
+            {
+                if ( logger.isLoggable( MLevel.FINE ) )
+                    logger.log(MLevel.FINE,"Exception on resource InputStream close.", e);
+            }
+        }
+    }
+
+    public static C3P0Config extractXmlConfigFromInputStream(InputStream is) throws Exception
+    {
+        DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
+        DocumentBuilder db = fact.newDocumentBuilder();
+        Document doc = db.parse( is );
+
+        return extractConfigFromXmlDoc(doc);
+    }
+
+    public static C3P0Config extractConfigFromXmlDoc(Document doc) throws Exception
+    {
+        Element docElem = doc.getDocumentElement();
+        if (docElem.getTagName().equals("c3p0-config"))
+        {
+            NamedScope defaults;
+            HashMap configNamesToNamedScopes = new HashMap();
+
+            Element defaultConfigElem = DomParseUtils.uniqueChild( docElem, "default-config" );
+            if (defaultConfigElem != null)
+                defaults = extractNamedScopeFromLevel( defaultConfigElem );
+            else
+                defaults = new NamedScope();
+            NodeList nl = DomParseUtils.immediateChildElementsByTagName(docElem, "named-config");
+            for (int i = 0, len = nl.getLength(); i < len; ++i)
+            {
+                Element namedConfigElem = (Element) nl.item(i);
+                String configName = namedConfigElem.getAttribute("name");
+                if (configName != null && configName.length() > 0)
+                {
+                    NamedScope namedConfig = extractNamedScopeFromLevel( namedConfigElem );
+                    configNamesToNamedScopes.put( configName, namedConfig);
+                }
+                else
+                    logger.warning("Configuration XML contained named-config element without name attribute: " + namedConfigElem);
+            }
+            return new C3P0Config( defaults, configNamesToNamedScopes );
+        }
+        else
+            throw new Exception("Root element of c3p0 config xml should be 'c3p0-config', not '" + docElem.getTagName() + "'.");
+    }
+
+    private static NamedScope extractNamedScopeFromLevel(Element elem)
+    {
+        HashMap props = extractPropertiesFromLevel( elem );
+        HashMap userNamesToOverrides = new HashMap();
+
+        NodeList nl = DomParseUtils.immediateChildElementsByTagName(elem, "user-overrides");
+        for (int i = 0, len = nl.getLength(); i < len; ++i)
+        {
+            Element perUserConfigElem = (Element) nl.item(i);
+            String userName = perUserConfigElem.getAttribute("user");
+            if (userName != null && userName.length() > 0)
+            {
+                HashMap userProps = extractPropertiesFromLevel( perUserConfigElem );
+                userNamesToOverrides.put( userName, userProps );
+            }
+            else
+                logger.warning("Configuration XML contained user-overrides element without user attribute: " + LINESEP + perUserConfigElem);
+        }
+
+        return new NamedScope(props, userNamesToOverrides);
+    }
+
+    private static HashMap extractPropertiesFromLevel(Element elem)
+    {
+        // System.err.println( "extractPropertiesFromLevel()" );
+
+        HashMap out = new HashMap();
+
+        try
+        {
+            NodeList nl = DomParseUtils.immediateChildElementsByTagName(elem, "property");
+            int len = nl.getLength();
+            for (int i = 0; i < len; ++i)
+            {
+                Element propertyElem = (Element) nl.item(i);
+                String propName = propertyElem.getAttribute("name");
+                if (propName != null && propName.length() > 0)
+                {
+                    String propVal = DomParseUtils.allTextFromElement(propertyElem, true);
+                    out.put( propName, propVal );
+                    //System.err.println( propName + " -> " + propVal );
+                }
+                else
+                    logger.warning("Configuration XML contained property element without name attribute: " + LINESEP + propertyElem);
+            }
+        }
+        catch (Exception e)
+        {
+            logger.log( MLevel.WARNING, 
+                            "An exception occurred while reading config XML. " +
+                            "Some configuration information has probably been ignored.", 
+                            e );
+        }
+
+        return out;
+    }
+
+    private C3P0ConfigXmlUtils()
+    {}
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/cfg/DefaultC3P0ConfigFinder.java b/src/classes/com/mchange/v2/c3p0/cfg/DefaultC3P0ConfigFinder.java
new file mode 100644
index 0000000..f12ef70
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/cfg/DefaultC3P0ConfigFinder.java
@@ -0,0 +1,88 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.cfg;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.Properties;
+import com.mchange.v2.cfg.MultiPropertiesConfig;
+
+public class DefaultC3P0ConfigFinder implements C3P0ConfigFinder
+{
+    final static String XML_CFG_FILE_KEY = "com.mchange.v2.c3p0.cfg.xml";
+
+    public C3P0Config findConfig() throws Exception
+    {
+	C3P0Config out;
+
+	HashMap flatDefaults = C3P0ConfigUtils.extractHardcodedC3P0Defaults();
+
+	// this includes System properties, but we have to check for System properties
+	// again, since we want system properties to override unspecified user, default-config
+	// properties in the XML
+	flatDefaults.putAll( C3P0ConfigUtils.extractC3P0PropertiesResources() );
+
+	String cfgFile = MultiPropertiesConfig.readVmConfig().getProperty( XML_CFG_FILE_KEY );
+	if (cfgFile == null)
+	    {
+		C3P0Config xmlConfig = C3P0ConfigXmlUtils.extractXmlConfigFromDefaultResource();
+		if (xmlConfig != null)
+		    {
+			insertDefaultsUnderNascentConfig( flatDefaults, xmlConfig );
+			out = xmlConfig;
+		    }
+		else
+		    out = C3P0ConfigUtils.configFromFlatDefaults( flatDefaults );
+	    }
+	else
+	    {
+		InputStream is = new BufferedInputStream( new FileInputStream( cfgFile ) );
+		try
+		    {
+			C3P0Config xmlConfig = C3P0ConfigXmlUtils.extractXmlConfigFromInputStream( is );
+			insertDefaultsUnderNascentConfig( flatDefaults, xmlConfig );
+			out = xmlConfig;
+		    }
+		finally
+		    {
+			try {is.close();}
+			catch (Exception e)
+			    { e.printStackTrace(); }
+		    }
+	    }
+
+	// overwrite default, unspecified user config with System properties
+	// defined values
+	Properties sysPropConfig = C3P0ConfigUtils.findAllC3P0SystemProperties();
+	out.defaultConfig.props.putAll( sysPropConfig );
+
+	return out;
+    }
+
+    private void insertDefaultsUnderNascentConfig(HashMap flatDefaults, C3P0Config config)
+    {
+	flatDefaults.putAll( config.defaultConfig.props );
+	config.defaultConfig.props = flatDefaults;
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/cfg/NamedScope.java b/src/classes/com/mchange/v2/c3p0/cfg/NamedScope.java
new file mode 100644
index 0000000..1299a16
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/cfg/NamedScope.java
@@ -0,0 +1,46 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.cfg;
+
+import java.util.*;
+
+//all internal maps should be HashMaps (the implementation presumes HashMaps)
+
+class NamedScope
+{
+    HashMap props;
+    HashMap userNamesToOverrides;
+
+    NamedScope()
+    {
+	this.props                = new HashMap();
+	this.userNamesToOverrides = new HashMap();
+    }
+
+    NamedScope( HashMap props, HashMap userNamesToOverrides)
+    {
+	this.props                = props;
+	this.userNamesToOverrides = userNamesToOverrides;
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/codegen/BeangenDataSourceGenerator.java b/src/classes/com/mchange/v2/c3p0/codegen/BeangenDataSourceGenerator.java
new file mode 100644
index 0000000..4eff81f
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/codegen/BeangenDataSourceGenerator.java
@@ -0,0 +1,230 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.codegen;
+
+import java.io.*;
+import java.util.*;
+import javax.xml.parsers.*;
+import org.w3c.dom.*;
+import com.mchange.v2.codegen.*;
+import com.mchange.v2.codegen.bean.*;
+import com.mchange.v2.c3p0.impl.*;
+
+import java.lang.reflect.Modifier;
+import com.mchange.v1.xml.DomParseUtils;
+
+public class BeangenDataSourceGenerator
+{
+    public static void main( String[] argv )
+    {
+	try
+	    {
+		if (argv.length != 2)
+		    {
+			System.err.println("java " + BeangenDataSourceGenerator.class.getName() + 
+					   " <infile.xml> <OutputFile.java>");
+			return;
+		    }
+		
+
+		File outFile = new File( argv[1] );
+		File parentDir = outFile.getParentFile();
+		if (! parentDir.exists())
+		    {
+			System.err.println("Warning: making parent directory: " + parentDir);
+			parentDir.mkdirs();
+		    }
+
+		DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
+		DocumentBuilder db = fact.newDocumentBuilder();
+		Document doc = db.parse( new File( argv[0] ) );
+		ParsedPropertyBeanDocument parsed = new ParsedPropertyBeanDocument( doc );
+		Writer w = new BufferedWriter( new FileWriter( outFile ) );
+
+		SimplePropertyBeanGenerator gen = new SimplePropertyBeanGenerator();
+		gen.setGeneratorName( BeangenDataSourceGenerator.class.getName() );
+
+		// tightly coupled to the implementation of SimplePropertyBeanGenerator!
+		IndirectingSerializableExtension idse = new IndirectingSerializableExtension("com.mchange.v2.naming.ReferenceIndirector")
+		    {
+			protected void generateExtraSerInitializers(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+			    throws IOException
+			{
+			    if (BeangenUtils.hasBoundProperties( props ))
+				iw.println("this.pcs = new PropertyChangeSupport( this );");
+			    if (BeangenUtils.hasConstrainedProperties( props ))
+				iw.println("this.vcs = new VetoableChangeSupport( this );");
+			}
+		    };
+		gen.addExtension( idse );
+
+		PropsToStringGeneratorExtension tsge = new PropsToStringGeneratorExtension();
+		tsge.setExcludePropertyNames( Arrays.asList( new String[] {"userOverridesAsString","overrideDefaultUser","overrideDefaultPassword"} ) );
+		gen.addExtension( tsge );
+
+		PropertyReferenceableExtension prex = new PropertyReferenceableExtension();
+		prex.setUseExplicitReferenceProperties( true );
+		// we use the string version to creating dependencies between the bean generator and c3p0 classes
+		//prex.setFactoryClassName( C3P0JavaBeanObjectFactory.class.getName() );
+		prex.setFactoryClassName( "com.mchange.v2.c3p0.impl.C3P0JavaBeanObjectFactory" );
+		gen.addExtension( prex );
+
+		BooleanInitIdentityTokenConstructortorGeneratorExtension biitcge = new BooleanInitIdentityTokenConstructortorGeneratorExtension();
+		gen.addExtension( biitcge );
+
+		if ( parsed.getClassInfo().getClassName().equals("WrapperConnectionPoolDataSourceBase") )
+		    gen.addExtension( new WcpdsExtrasGeneratorExtension() );
+
+		if (unmodifiableShadow( doc ) )
+		    gen.addExtension( new UnmodifiableShadowGeneratorExtension() );
+
+
+		gen.generate( parsed.getClassInfo(), parsed.getProperties(), w );
+
+		w.flush();
+		w.close();
+
+		System.err.println("Processed: " + argv[0] ); //+ " -> " + argv[1]);
+	    }
+	catch ( Exception e )
+	    { e.printStackTrace(); }
+    }
+
+    private static boolean unmodifiableShadow( Document doc )
+    {
+	Element docElem = doc.getDocumentElement();
+	return DomParseUtils.uniqueChild(docElem, "unmodifiable-shadow") != null;
+    }
+
+    static class BooleanInitIdentityTokenConstructortorGeneratorExtension implements GeneratorExtension
+    {
+	public Collection extraGeneralImports()  {return Collections.EMPTY_SET;} 
+
+	public Collection extraSpecificImports() 
+	{
+	    Set out = new HashSet();
+	    out.add( "com.mchange.v2.c3p0.C3P0Registry" );
+	    return out;
+	}
+
+	public Collection extraInterfaceNames()  {return Collections.EMPTY_SET;}
+
+	public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	    throws IOException
+	{
+	    BeangenUtils.writeExplicitDefaultConstructor( Modifier.PRIVATE, info, iw);
+	    iw.println();
+	    iw.println("public " + info.getClassName() + "( boolean autoregister )");
+	    iw.println("{");
+	    iw.upIndent();
+	    iw.println( "if (autoregister)");
+	    iw.println("{");
+	    iw.upIndent();
+	    iw.println("this.identityToken = C3P0ImplUtils.allocateIdentityToken( this );");
+	    iw.println("C3P0Registry.reregister( this );");
+	    iw.downIndent();
+	    iw.println("}");
+
+	    iw.downIndent();
+	    iw.println("}");
+	}
+    }
+
+    static class WcpdsExtrasGeneratorExtension implements GeneratorExtension
+    {
+	public Collection extraGeneralImports()  {return Collections.EMPTY_SET;} 
+
+	public Collection extraSpecificImports() 
+	{
+	    Set out = new HashSet();
+	    out.add( "com.mchange.v2.c3p0.ConnectionCustomizer" );
+	    out.add( "javax.sql.PooledConnection" );
+	    out.add( "java.sql.SQLException" );
+	    return out;
+	}
+
+	public Collection extraInterfaceNames()  {return Collections.EMPTY_SET;}
+
+	public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	    throws IOException
+	{
+	    iw.println("protected abstract PooledConnection getPooledConnection( ConnectionCustomizer cc, String idt)" +
+		       " throws SQLException;");
+	    iw.println("protected abstract PooledConnection getPooledConnection(String user, String password, ConnectionCustomizer cc, String idt)" +
+		       " throws SQLException;");
+	}
+    }
+
+
+    static class UnmodifiableShadowGeneratorExtension implements GeneratorExtension
+    {
+	BeanExtractingGeneratorExtension      bege;
+	CompleteConstructorGeneratorExtension ccge;
+
+	{
+	    bege = new BeanExtractingGeneratorExtension();
+	    bege.setExtractMethodModifiers( Modifier.PRIVATE );
+	    bege.setConstructorModifiers( Modifier.PUBLIC );
+
+	    ccge = new CompleteConstructorGeneratorExtension();
+	}	
+
+	public Collection extraGeneralImports()  
+	{
+	    Set out = new HashSet();
+	    out.addAll( bege.extraGeneralImports() );
+	    out.addAll( ccge.extraGeneralImports() );
+	    return out;
+	}
+
+	public Collection extraSpecificImports() 
+	{
+	    Set out = new HashSet();
+	    out.addAll( bege.extraSpecificImports() );
+	    out.addAll( ccge.extraSpecificImports() );
+	    return out;
+	}
+
+	public Collection extraInterfaceNames()  {return Collections.EMPTY_SET;}
+
+	public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	    throws IOException
+	{
+	    ClassInfo innerInfo = new SimpleClassInfo( info.getPackageName(), 
+						       Modifier.PUBLIC | Modifier.STATIC, 
+						       "UnmodifiableShadow", 
+						       info.getSuperclassName(),
+						       info.getInterfaceNames(),
+						       info.getGeneralImports(),
+						       info.getSpecificImports() );
+
+	    SimplePropertyBeanGenerator innerGen = new SimplePropertyBeanGenerator();
+	    innerGen.setInner( true );
+	    innerGen.setForceUnmodifiable( true );
+	    innerGen.addExtension( bege );
+	    innerGen.addExtension( ccge );
+	    innerGen.generate( innerInfo, props, iw );
+	}
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/codegen/JdbcProxyGenerator.java b/src/classes/com/mchange/v2/c3p0/codegen/JdbcProxyGenerator.java
new file mode 100644
index 0000000..e72e46b
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/codegen/JdbcProxyGenerator.java
@@ -0,0 +1,1018 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.codegen;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.sql.*;
+import com.mchange.v2.codegen.*;
+import com.mchange.v2.codegen.intfc.*;
+import com.mchange.v2.c3p0.C3P0ProxyConnection;
+import com.mchange.v2.c3p0.C3P0ProxyStatement;
+
+public abstract class JdbcProxyGenerator extends DelegatorGenerator
+{
+    final static boolean PREMATURE_DETACH_DEBUG = false;
+
+    JdbcProxyGenerator()
+    {
+        this.setGenerateInnerSetter( false );
+        this.setGenerateInnerGetter( false );
+        this.setGenerateNoArgConstructor( false );
+        this.setGenerateWrappingConstructor( true );
+        this.setClassModifiers( Modifier.PUBLIC | Modifier.FINAL );
+        this.setMethodModifiers( Modifier.PUBLIC | Modifier.FINAL );
+    }
+
+    abstract String getInnerTypeName();
+
+    static final class NewProxyMetaDataGenerator extends JdbcProxyGenerator
+    { 
+        String getInnerTypeName()
+        { return "DatabaseMetaData"; }
+
+        protected void generateDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+        {
+            String mname   = method.getName();
+            Class  retType = method.getReturnType();
+
+            if ( ResultSet.class.isAssignableFrom( retType ) )
+            {
+                iw.println("ResultSet innerResultSet = inner." + CodegenUtils.methodCall( method ) + ";");
+                iw.println("if (innerResultSet == null) return null;");
+                iw.println("return new NewProxyResultSet( innerResultSet, parentPooledConnection, inner, this );"); 
+            }
+            else if ( mname.equals( "getConnection" ) )
+            {
+                iw.println("return this.proxyCon;");
+            }
+            else
+                super.generateDelegateCode( intfcl, genclass, method, iw );
+        }
+
+        protected void generatePreDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+        {
+            if ( method.getExceptionTypes().length > 0 )
+                super.generatePreDelegateCode( intfcl, genclass, method, iw );
+        }
+
+        protected void generatePostDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+        {
+            if ( method.getExceptionTypes().length > 0 )
+                super.generatePostDelegateCode( intfcl, genclass, method, iw );
+        }
+
+        protected void generateExtraDeclarations( Class intfcl, String genclass, IndentedWriter iw ) throws IOException
+        {
+            super.generateExtraDeclarations( intfcl, genclass, iw );
+            iw.println();
+            iw.println("NewProxyConnection proxyCon;");
+            iw.println();
+            iw.print( CodegenUtils.fqcnLastElement( genclass ) );
+            iw.println("( " + CodegenUtils.simpleClassName( intfcl ) + " inner, NewPooledConnection parentPooledConnection, NewProxyConnection proxyCon )");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("this( inner, parentPooledConnection );");
+            iw.println("this.proxyCon = proxyCon;");
+            iw.downIndent();
+            iw.println("}");
+        }
+    }
+
+    static final class NewProxyResultSetGenerator extends JdbcProxyGenerator
+    {
+        String getInnerTypeName()
+        { return "ResultSet"; }
+
+        protected void generateDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+        {
+            iw.println("if (proxyConn != null) proxyConn.maybeDirtyTransaction();");
+            iw.println();
+            String mname   = method.getName();
+            Class  retType = method.getReturnType();
+
+            if ( mname.equals("close") )
+            {
+                iw.println("if (! this.isDetached())");
+                iw.println("{");
+                iw.upIndent();
+
+                iw.println("if (creator instanceof Statement)");
+                iw.upIndent();
+                iw.println("parentPooledConnection.markInactiveResultSetForStatement( (Statement) creator, inner );");
+                iw.downIndent();
+                iw.println("else if (creator instanceof DatabaseMetaData)");
+                iw.upIndent();
+                iw.println("parentPooledConnection.markInactiveMetaDataResultSet( inner );");
+                iw.downIndent();
+                iw.println("else if (creator instanceof Connection)");
+                iw.upIndent();
+                iw.println("parentPooledConnection.markInactiveRawConnectionResultSet( inner );");
+                iw.downIndent();
+                iw.println("else throw new InternalError(\042Must be Statement or DatabaseMetaData -- Bad Creator: \042 + creator);");
+
+                iw.println("this.detach();");
+                iw.println("inner.close();");
+                iw.println("this.inner = null;");
+
+                iw.downIndent();
+                iw.println("}");
+            }
+            else if ( mname.equals("getStatement") )
+            {
+                iw.println("if (creator instanceof Statement)");
+                iw.upIndent();
+                iw.println("return (Statement) creatorProxy;");
+                iw.downIndent();
+                iw.println("else if (creator instanceof DatabaseMetaData)");
+                iw.upIndent();
+                iw.println("return null;");
+                iw.downIndent();
+                iw.println("else throw new InternalError(\042Must be Statement or DatabaseMetaData -- Bad Creator: \042 + creator);");
+            }
+            else if ( mname.equals("isClosed") )
+            {
+                iw.println( "return this.isDetached();" );
+            }
+            else
+                super.generateDelegateCode( intfcl, genclass, method, iw );
+        }
+
+        protected void generateExtraDeclarations( Class intfcl, String genclass, IndentedWriter iw ) throws IOException
+        {
+            super.generateExtraDeclarations( intfcl, genclass, iw );
+            iw.println();
+            iw.println("Object creator;");
+            iw.println("Object creatorProxy;");
+            iw.println("NewProxyConnection proxyConn;");
+            iw.println();
+            iw.print( CodegenUtils.fqcnLastElement( genclass ) );
+            iw.println("( " + CodegenUtils.simpleClassName( intfcl ) + " inner, NewPooledConnection parentPooledConnection, Object c, Object cProxy )");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("this( inner, parentPooledConnection );");
+            iw.println("this.creator      = c;");
+            iw.println("this.creatorProxy = cProxy;");
+            iw.println("if (creatorProxy instanceof NewProxyConnection) this.proxyConn = (NewProxyConnection) cProxy;");
+            iw.downIndent();
+            iw.println("}");
+        }
+
+        protected void generatePreDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+        {
+            super.generatePreDelegateCode( intfcl, genclass, method, iw );
+        }
+    }
+
+    static final class NewProxyAnyStatementGenerator extends JdbcProxyGenerator
+    {
+        String getInnerTypeName()
+        { return "Statement"; }
+
+        private final static boolean CONCURRENT_ACCESS_DEBUG = false;
+
+        {
+            this.setExtraInterfaces( new Class[] { C3P0ProxyStatement.class } );
+        }
+
+        protected void generateDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+        {
+            iw.println("maybeDirtyTransaction();");
+            iw.println();
+
+            String mname   = method.getName();
+            Class  retType = method.getReturnType();
+
+            if ( ResultSet.class.isAssignableFrom( retType ) )
+            {
+                iw.println("ResultSet innerResultSet = inner." + CodegenUtils.methodCall( method ) + ";");
+                iw.println("if (innerResultSet == null) return null;");
+                iw.println("parentPooledConnection.markActiveResultSetForStatement( inner, innerResultSet );");
+                iw.println("return new NewProxyResultSet( innerResultSet, parentPooledConnection, inner, this );"); 
+            }
+            else if ( mname.equals("getConnection") )
+            {
+                iw.println("if (! this.isDetached())");
+                iw.upIndent();
+                iw.println("return creatorProxy;");
+                iw.downIndent();
+                iw.println("else");
+                iw.upIndent();
+                iw.println("throw new SQLException(\"You cannot operate on a closed Statement!\");");
+                iw.downIndent();
+            }
+            else if ( mname.equals("close") )
+            {
+                iw.println("if (! this.isDetached())");
+                iw.println("{");
+                iw.upIndent();
+
+                iw.println("if ( is_cached )");
+                iw.upIndent();
+                iw.println("parentPooledConnection.checkinStatement( inner );");
+                iw.downIndent();
+                iw.println("else");
+                iw.println("{");
+                iw.upIndent();
+                iw.println("parentPooledConnection.markInactiveUncachedStatement( inner );");
+
+                iw.println("try{ inner.close(); }");
+                iw.println("catch (Exception e )");
+                iw.println("{");
+                iw.upIndent();
+
+                iw.println("if (logger.isLoggable( MLevel.WARNING ))");
+                iw.upIndent();
+                iw.println("logger.log( MLevel.WARNING, \042Exception on close of inner statement.\042, e);");
+                iw.downIndent();
+
+                iw.println( "SQLException sqle = SqlUtils.toSQLException( e );" );
+                iw.println( "throw sqle;" );
+                iw.downIndent();
+                iw.println("}");
+                iw.downIndent();
+                iw.println("}");
+
+                iw.println();
+                iw.println("this.detach();");
+                iw.println("this.inner = null;");
+                iw.println("this.creatorProxy = null;");
+
+                iw.downIndent();
+                iw.println("}");
+            }
+            else if ( mname.equals("isClosed") )
+            {
+                iw.println( "return this.isDetached();" );
+            }
+            else
+                super.generateDelegateCode( intfcl, genclass, method, iw );
+        }
+
+        protected void generatePreDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+        {
+            // concurrent-access-debug only
+            if (CONCURRENT_ACCESS_DEBUG)
+            {
+                iw.println("Object record;");
+                iw.println("synchronized (concurrentAccessRecorder)");
+                iw.println("{");
+                iw.upIndent();
+
+                iw.println("record = concurrentAccessRecorder.record();");
+                iw.println("int num_concurrent_clients = concurrentAccessRecorder.size();");
+                iw.println("if (num_concurrent_clients != 1)");
+                iw.upIndent();
+                iw.println("logger.log(MLevel.WARNING, " +
+                "concurrentAccessRecorder.getDump(\042Apparent concurrent access! (\042 + num_concurrent_clients + \042 clients.\042) );");
+                iw.downIndent();
+                iw.downIndent();
+                iw.println("}");
+                iw.println();
+            }
+            // end concurrent-access-debug only
+
+            super.generatePreDelegateCode( intfcl, genclass, method, iw );
+        }
+
+        protected void generatePostDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+        {
+            super.generatePostDelegateCode( intfcl, genclass, method, iw );
+
+            // concurrent-access-debug only
+            if (CONCURRENT_ACCESS_DEBUG)
+            {
+                iw.println("finally");
+                iw.println("{");
+                iw.upIndent();
+                iw.println("concurrentAccessRecorder.remove( record );");
+                iw.downIndent();
+                iw.println("}");
+            }
+            // end concurrent-access-debug only
+        }
+
+        protected void generateExtraDeclarations( Class intfcl, String genclass, IndentedWriter iw ) throws IOException
+        {
+            super.generateExtraDeclarations( intfcl, genclass, iw );
+            iw.println();
+
+            // concurrent-access-debug only!
+            if (CONCURRENT_ACCESS_DEBUG)
+            {
+                iw.println("com.mchange.v2.debug.ThreadNameStackTraceRecorder concurrentAccessRecorder");
+                iw.upIndent();
+                iw.println("= new com.mchange.v2.debug.ThreadNameStackTraceRecorder(\042Concurrent Access Recorder\042);");
+                iw.downIndent();
+            }
+            // end concurrent-access-debug only!
+
+            iw.println("boolean is_cached;");
+            iw.println("NewProxyConnection creatorProxy;");
+            iw.println();
+            iw.print( CodegenUtils.fqcnLastElement( genclass ) );
+            iw.println("( " + CodegenUtils.simpleClassName( intfcl ) + 
+            " inner, NewPooledConnection parentPooledConnection, boolean cached, NewProxyConnection cProxy )");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("this( inner, parentPooledConnection );");
+            iw.println("this.is_cached = cached;");
+            iw.println("this.creatorProxy = cProxy;");
+            iw.downIndent();
+            iw.println("}");
+            iw.println();
+            iw.println("public Object rawStatementOperation(Method m, Object target, Object[] args) " +
+            "throws IllegalAccessException, InvocationTargetException, SQLException");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("maybeDirtyTransaction();");
+            iw.println();
+            iw.println("if (target == C3P0ProxyStatement.RAW_STATEMENT) target = inner;");
+            iw.println("for (int i = 0, len = args.length; i < len; ++i)");
+            iw.upIndent();
+            iw.println("if (args[i] == C3P0ProxyStatement.RAW_STATEMENT) args[i] = inner;");
+            iw.downIndent();
+            iw.println("Object out = m.invoke(target, args);");
+            iw.println("if (out instanceof ResultSet)");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("ResultSet innerResultSet = (ResultSet) out;");
+            iw.println("parentPooledConnection.markActiveResultSetForStatement( inner, innerResultSet );");
+            iw.println("out = new NewProxyResultSet( innerResultSet, parentPooledConnection, inner, this );"); 
+            iw.downIndent();
+            iw.println("}");
+            iw.println();
+            iw.println("return out;");
+            iw.downIndent();
+            iw.println("}");
+            iw.println();
+            iw.println("void maybeDirtyTransaction()");
+            iw.println("{ creatorProxy.maybeDirtyTransaction(); }");
+        }
+
+        protected void generateExtraImports( IndentedWriter iw ) throws IOException
+        {
+            super.generateExtraImports( iw );
+            iw.println("import java.lang.reflect.InvocationTargetException;");
+        }
+
+
+    }
+
+//  protected void generateExtraDeclarations( Class intfcl, String genclass, IndentedWriter iw ) throws IOException
+//  {
+//  super.generateExtraDeclarations( intfcl, genclass, iw );
+//  iw.println();
+//  iw.println("Statement creatingStatement;");
+//  iw.println();
+//  iw.print( CodegenUtils.fqcnLastElement( genclass ) );
+//  iw.println("( " + CodegenUtils.simpleClassName( intfcl.getClass() ) + " inner, NewPooledConnection parentPooledConnection, Statement stmt )");
+//  iw.println("{");
+//  iw.upIndent();
+//  iw.println("this( inner, parentPooledConnection );");
+//  iw.println("this.creatingStatement = stmt;");
+//  iw.downIndent();
+//  iw.println("}");
+//  }
+
+    static final class NewProxyConnectionGenerator extends JdbcProxyGenerator
+    {
+        String getInnerTypeName()
+        { return "Connection"; }
+
+        {
+            this.setMethodModifiers( Modifier.PUBLIC | Modifier.SYNCHRONIZED );
+            this.setExtraInterfaces( new Class[] { C3P0ProxyConnection.class } );
+        }
+
+        protected void generateDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+        {
+            String mname = method.getName();
+            if (mname.equals("createStatement"))
+            {
+                iw.println("txn_known_resolved = false;");
+                iw.println();
+                iw.println("Statement innerStmt = inner."  + CodegenUtils.methodCall( method ) + ";");
+                iw.println("parentPooledConnection.markActiveUncachedStatement( innerStmt );");
+                iw.println("return new NewProxyStatement( innerStmt, parentPooledConnection, false, this );");
+            }
+            else if (mname.equals("prepareStatement"))
+            {
+                iw.println("txn_known_resolved = false;");
+                iw.println();
+                iw.println("PreparedStatement innerStmt;");
+                iw.println();
+                iw.println("if ( parentPooledConnection.isStatementCaching() )");
+                iw.println("{");
+                iw.upIndent();
+
+                iw.println("try");
+                iw.println("{");
+                iw.upIndent();
+
+                generateFindMethodAndArgs( method, iw );
+                iw.println("innerStmt = (PreparedStatement) parentPooledConnection.checkoutStatement( method, args );");
+                iw.println("return new NewProxyPreparedStatement( innerStmt, parentPooledConnection, true, this );");
+
+                iw.downIndent();
+                iw.println("}");
+                iw.println("catch (ResourceClosedException e)");
+                iw.println("{");
+                iw.upIndent();
+
+                iw.println("if ( logger.isLoggable( MLevel.FINE ) )");
+                iw.upIndent();
+                iw.println("logger.log( MLevel.FINE, " +
+                           "\042A Connection tried to prepare a Statement via a Statement cache that is already closed. " +
+                           "This can happen -- rarely -- if a DataSource is closed or reset() while Connections are checked-out and in use.\042, e );");
+                iw.downIndent();
+
+                // repeated code... any changes probably need to be duplicated below
+                iw.println("innerStmt = inner."  + CodegenUtils.methodCall( method ) + ";");
+                iw.println("parentPooledConnection.markActiveUncachedStatement( innerStmt );");
+                iw.println("return new NewProxyPreparedStatement( innerStmt, parentPooledConnection, false, this );");
+
+                iw.downIndent();
+                iw.println("}");
+
+                iw.downIndent();
+                iw.println("}");
+                iw.println("else");
+                iw.println("{");
+                iw.upIndent();
+
+                // repeated code... any changes probably need to be duplicated above
+                iw.println("innerStmt = inner."  + CodegenUtils.methodCall( method ) + ";");
+                iw.println("parentPooledConnection.markActiveUncachedStatement( innerStmt );");
+                iw.println("return new NewProxyPreparedStatement( innerStmt, parentPooledConnection, false, this );");
+
+                iw.downIndent();
+                iw.println("}");
+
+            }
+            else if (mname.equals("prepareCall"))
+            {
+                iw.println("txn_known_resolved = false;");
+                iw.println();
+                iw.println("CallableStatement innerStmt;");
+                iw.println();
+                iw.println("if ( parentPooledConnection.isStatementCaching() )");
+                iw.println("{");
+                iw.upIndent();
+
+                iw.println("try");
+                iw.println("{");
+                iw.upIndent();
+
+                generateFindMethodAndArgs( method, iw );
+                iw.println("innerStmt = (CallableStatement) parentPooledConnection.checkoutStatement( method, args );");
+                iw.println("return new NewProxyCallableStatement( innerStmt, parentPooledConnection, true, this );");
+
+                iw.downIndent();
+                iw.println("}");
+                iw.println("catch (ResourceClosedException e)");
+                iw.println("{");
+                iw.upIndent();
+
+                iw.println("if ( logger.isLoggable( MLevel.FINE ) )");
+                iw.upIndent();
+                iw.println("logger.log( MLevel.FINE, " +
+                           "\042A Connection tried to prepare a CallableStatement via a Statement cache that is already closed. " +
+                           "This can happen -- rarely -- if a DataSource is closed or reset() while Connections are checked-out and in use.\042, e );");
+                iw.downIndent();
+
+                // repeated code... any changes probably need to be duplicated below
+                iw.println("innerStmt = inner." + CodegenUtils.methodCall( method ) + ";");
+                iw.println("parentPooledConnection.markActiveUncachedStatement( innerStmt );");
+                iw.println("return new NewProxyCallableStatement( innerStmt, parentPooledConnection, false, this );");
+
+                iw.downIndent();
+                iw.println("}");
+
+                iw.downIndent();
+                iw.println("}");
+                iw.println("else");
+                iw.println("{");
+                iw.upIndent();
+
+                // repeated code... any changes probably need to be duplicated above
+                iw.println("innerStmt = inner." + CodegenUtils.methodCall( method ) + ";");
+                iw.println("parentPooledConnection.markActiveUncachedStatement( innerStmt );");
+                iw.println("return new NewProxyCallableStatement( innerStmt, parentPooledConnection, false, this );");
+
+                iw.downIndent();
+                iw.println("}");
+
+            }
+            else if (mname.equals("getMetaData"))
+            {
+                iw.println("txn_known_resolved = false;");
+                iw.println();
+                iw.println("if (this.metaData == null)");
+                iw.println("{");
+                iw.upIndent();
+                iw.println("DatabaseMetaData innerMetaData = inner." + CodegenUtils.methodCall( method ) + ";");
+                iw.println("this.metaData = new NewProxyDatabaseMetaData( innerMetaData, parentPooledConnection, this );");
+                iw.downIndent();
+                iw.println("}");
+                iw.println("return this.metaData;");
+            }
+            else if ( mname.equals("setTransactionIsolation") )
+            {
+                //do nothing with txn_known_resolved
+
+                super.generateDelegateCode( intfcl, genclass, method, iw );
+                iw.println( "parentPooledConnection.markNewTxnIsolation( " +  CodegenUtils.generatedArgumentName( 0 ) + " );");
+            }
+            else if ( mname.equals("setCatalog") )
+            {
+                //do nothing with txn_known_resolved
+
+                super.generateDelegateCode( intfcl, genclass, method, iw );
+                iw.println( "parentPooledConnection.markNewCatalog( " +  CodegenUtils.generatedArgumentName( 0 ) + " );");
+            }
+            else if ( mname.equals("setHoldability") )
+            {
+                //do nothing with txn_known_resolved
+
+                super.generateDelegateCode( intfcl, genclass, method, iw );
+                iw.println( "parentPooledConnection.markNewHoldability( " +  CodegenUtils.generatedArgumentName( 0 ) + " );");
+            }
+            else if ( mname.equals("setReadOnly") )
+            {
+                //do nothing with txn_known_resolved
+
+                super.generateDelegateCode( intfcl, genclass, method, iw );
+                iw.println( "parentPooledConnection.markNewReadOnly( " +  CodegenUtils.generatedArgumentName( 0 ) + " );");
+            }
+            else if ( mname.equals("setTypeMap") )
+            {
+                //do nothing with txn_known_resolved
+
+                super.generateDelegateCode( intfcl, genclass, method, iw );
+                iw.println( "parentPooledConnection.markNewTypeMap( " +  CodegenUtils.generatedArgumentName( 0 ) + " );");
+            }
+            else if ( mname.equals("close") )
+            {
+                iw.println("if (! this.isDetached())");
+                iw.println("{");
+                iw.upIndent();
+                iw.println("NewPooledConnection npc = parentPooledConnection;");
+                iw.println("this.detach();");
+                iw.println("npc.markClosedProxyConnection( this, txn_known_resolved );");
+                iw.println("this.inner = null;");
+                iw.downIndent();
+                iw.println("}");
+                iw.println("else if (Debug.DEBUG && logger.isLoggable( MLevel.FINE ))");
+                iw.println("{");
+                iw.upIndent();
+                iw.println("logger.log( MLevel.FINE, this + \042: close() called more than once.\042 );");
+
+                // premature-detach-debug-debug only!
+                if (PREMATURE_DETACH_DEBUG)
+                {
+                    iw.println("prematureDetachRecorder.record();");
+                    iw.println("logger.warning( prematureDetachRecorder.getDump(\042Apparent multiple close of " + 
+                                    getInnerTypeName() + ".\042) );");
+                }
+                // end-premature-detach-debug-only!
+
+                iw.downIndent();
+                iw.println("}");
+            }
+            else if ( mname.equals("isClosed") )
+            {
+                iw.println("return this.isDetached();");
+            }
+            else
+            {
+                iw.println("txn_known_resolved = " + 
+                                ( mname.equals("commit") || mname.equals( "rollback" ) || mname.equals( "setAutoCommit" ) ) +
+                ';');
+                iw.println();
+                super.generateDelegateCode( intfcl, genclass, method, iw );
+            }
+        }
+
+        protected void generateExtraDeclarations( Class intfcl, String genclass, IndentedWriter iw ) throws IOException
+        {
+            iw.println("boolean txn_known_resolved = true;");
+            iw.println();
+            iw.println("DatabaseMetaData metaData = null;");
+            iw.println();
+
+//          We've nothing to do with preferredTestQuery here... the stuff below was unnecessary
+
+//          iw.println("String preferredTestQuery = null;");
+//          iw.println();
+//          iw.print( CodegenUtils.fqcnLastElement( genclass ) );
+//          iw.println("( " + CodegenUtils.simpleClassName( intfcl ) + " inner, NewPooledConnection parentPooledConnection, String preferredTestQuery )");
+//          iw.println("{");
+//          iw.upIndent();
+//          iw.println("this( inner, parentPooledConnection );");
+//          iw.println("this.preferredTestQuery = preferredTestQuery;");
+//          iw.downIndent();
+//          iw.println("}");
+//          iw.println();
+
+            iw.println("public Object rawConnectionOperation(Method m, Object target, Object[] args)");
+            iw.upIndent();
+            iw.println("throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SQLException");
+            iw.downIndent();
+            iw.println("{");
+            iw.upIndent();
+            iw.println("maybeDirtyTransaction();");
+            iw.println();
+            iw.println("if (inner == null)");
+            iw.upIndent();
+            iw.println("throw new SQLException(\"You cannot operate on a closed Connection!\");");
+            iw.downIndent();
+
+            iw.println("if ( target == C3P0ProxyConnection.RAW_CONNECTION)");
+            iw.upIndent();
+            iw.println("target = inner;");
+            iw.downIndent();
+
+            iw.println("for (int i = 0, len = args.length; i < len; ++i)");
+            iw.upIndent();
+            iw.println("if (args[i] == C3P0ProxyConnection.RAW_CONNECTION)");
+            iw.upIndent();
+            iw.println("args[i] = inner;");
+            iw.downIndent();
+            iw.downIndent();
+
+            iw.println("Object out = m.invoke( target, args );");
+            iw.println();
+            iw.println("// we never cache Statements generated by an operation on the raw Connection");
+            iw.println("if (out instanceof CallableStatement)");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("CallableStatement innerStmt = (CallableStatement) out;");
+            iw.println("parentPooledConnection.markActiveUncachedStatement( innerStmt );");
+            iw.println("out = new NewProxyCallableStatement( innerStmt, parentPooledConnection, false, this );");
+            iw.downIndent();
+            iw.println("}");
+            iw.println("else if (out instanceof PreparedStatement)");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("PreparedStatement innerStmt = (PreparedStatement) out;");
+            iw.println("parentPooledConnection.markActiveUncachedStatement( innerStmt );");
+            iw.println("out = new NewProxyPreparedStatement( innerStmt, parentPooledConnection, false, this );");
+            iw.downIndent();
+            iw.println("}");
+            iw.println("else if (out instanceof Statement)");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("Statement innerStmt = (Statement) out;");
+            iw.println("parentPooledConnection.markActiveUncachedStatement( innerStmt );");
+            iw.println("out = new NewProxyStatement( innerStmt, parentPooledConnection, false, this );");
+            iw.downIndent();
+            iw.println("}");
+            iw.println("else if (out instanceof ResultSet)");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("ResultSet innerRs = (ResultSet) out;");
+            iw.println("parentPooledConnection.markActiveRawConnectionResultSet( innerRs );");
+            iw.println("out = new NewProxyResultSet( innerRs, parentPooledConnection, inner, this );");	
+            iw.downIndent();
+            iw.println("}");
+            iw.println("else if (out instanceof DatabaseMetaData)");
+            iw.upIndent();
+            iw.println("out = new NewProxyDatabaseMetaData( (DatabaseMetaData) out, parentPooledConnection );");
+            iw.downIndent();
+            iw.println("return out;");
+            iw.downIndent();
+            iw.println("}");
+            iw.println();
+            iw.println("synchronized void maybeDirtyTransaction()");
+            iw.println("{ txn_known_resolved = false; }");
+
+            super.generateExtraDeclarations( intfcl, genclass, iw );
+        }
+
+        void generateFindMethodAndArgs( Method method, IndentedWriter iw ) throws IOException
+        {
+            iw.println("Class[] argTypes = ");
+            iw.println("{");
+            iw.upIndent();
+
+            Class[] argTypes = method.getParameterTypes();
+            for (int i = 0, len = argTypes.length; i < len; ++i)
+            {
+                if (i != 0) iw.println(",");
+                iw.print( CodegenUtils.simpleClassName( argTypes[i] ) + ".class" );
+            }
+            iw.println();
+            iw.downIndent();
+            iw.println("};");
+            iw.println("Method method = Connection.class.getMethod( \042" + method.getName() + "\042 , argTypes );");
+            iw.println();
+            iw.println("Object[] args = ");
+            iw.println("{");
+            iw.upIndent();
+
+            for (int i = 0, len = argTypes.length; i < len; ++i)
+            {
+                if (i != 0) iw.println(",");
+                String argName = CodegenUtils.generatedArgumentName( i );
+                Class argType = argTypes[i];
+                if (argType.isPrimitive())
+                {
+                    if (argType == boolean.class)
+                        iw.print( "Boolean.valueOf( " + argName + " )" );
+                    else if (argType == byte.class)
+                        iw.print( "new Byte( " + argName + " )" );
+                    else if (argType == char.class)
+                        iw.print( "new Character( " + argName + " )" );
+                    else if (argType == short.class)
+                        iw.print( "new Short( " + argName + " )" );
+                    else if (argType == int.class)
+                        iw.print( "new Integer( " + argName + " )" );
+                    else if (argType == long.class)
+                        iw.print( "new Long( " + argName + " )" );
+                    else if (argType == float.class)
+                        iw.print( "new Float( " + argName + " )" );
+                    else if (argType == double.class)
+                        iw.print( "new Double( " + argName + " )" );
+                }
+                else
+                    iw.print( argName );
+            }
+
+            iw.downIndent();
+            iw.println("};");
+        }
+
+        protected void generateExtraImports( IndentedWriter iw ) throws IOException
+        {
+            super.generateExtraImports( iw );
+            iw.println("import java.lang.reflect.InvocationTargetException;");
+            iw.println("import com.mchange.v2.util.ResourceClosedException;");
+        }
+    }
+
+    protected void generatePreDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+    {
+        generateTryOpener( iw );
+    }
+
+    protected void generatePostDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+    {
+        generateTryCloserAndCatch( intfcl, genclass, method, iw );
+    }
+
+    void generateTryOpener( IndentedWriter iw ) throws IOException
+    {
+        iw.println("try");
+        iw.println("{");
+        iw.upIndent();
+    }
+
+    void generateTryCloserAndCatch( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException
+    {
+        iw.downIndent();
+        iw.println("}");
+        iw.println("catch (NullPointerException exc)");
+        iw.println("{");
+        iw.upIndent();
+        iw.println("if ( this.isDetached() )");
+        iw.println("{");
+        iw.upIndent();
+        //iw.println( "System.err.print(\042probably 'cuz we're closed -- \042);" );
+        //iw.println( "exc.printStackTrace();" );
+        if ( "close".equals( method.getName() ) )
+        {
+            iw.println("if (Debug.DEBUG && logger.isLoggable( MLevel.FINE ))");
+            iw.println("{");
+            iw.upIndent();
+            iw.println("logger.log( MLevel.FINE, this + \042: close() called more than once.\042 );");
+
+            // premature-detach-debug-debug only!
+            if (PREMATURE_DETACH_DEBUG)
+            {
+                iw.println("prematureDetachRecorder.record();");
+                iw.println("logger.warning( prematureDetachRecorder.getDump(\042Apparent multiple close of " + 
+                                getInnerTypeName() + ".\042) );");
+            }
+            // end-premature-detach-debug-only!
+
+            iw.downIndent();
+            iw.println("}");
+        }
+        else
+        {
+            // premature-detach-debug-debug only!
+            if (PREMATURE_DETACH_DEBUG)
+            {
+                iw.println("prematureDetachRecorder.record();");
+                iw.println("logger.warning( prematureDetachRecorder.getDump(\042Use of already detached " + 
+                                getInnerTypeName() + ".\042) );");
+            }
+            // end-premature-detach-debug-only!
+
+            iw.println( "throw SqlUtils.toSQLException(\042You can't operate on a closed " + getInnerTypeName() + "!!!\042, exc);");
+        }
+        iw.downIndent();
+        iw.println("}");
+        iw.println( "else throw exc;" );
+        iw.downIndent();
+        iw.println("}");
+        iw.println("catch (Exception exc)");
+        iw.println("{");
+        iw.upIndent();
+        iw.println("if (! this.isDetached())");
+        iw.println("{");
+        iw.upIndent();
+        //iw.println( "exc.printStackTrace();" );
+        iw.println( "throw parentPooledConnection.handleThrowable( exc );" );
+        iw.downIndent();
+        iw.println("}");
+        iw.println("else throw SqlUtils.toSQLException( exc );");
+        iw.downIndent();
+        iw.println("}");
+    }
+
+    protected void generateExtraDeclarations( Class intfcl, String genclass, IndentedWriter iw ) throws IOException
+    {
+        // premature-detach-debug-debug only!
+        if (PREMATURE_DETACH_DEBUG)
+        {
+            iw.println("com.mchange.v2.debug.ThreadNameStackTraceRecorder prematureDetachRecorder");
+            iw.upIndent();
+            iw.println("= new com.mchange.v2.debug.ThreadNameStackTraceRecorder(\042Premature Detach Recorder\042);");
+            iw.downIndent();
+        }
+        // end-premature-detach-debug-only!
+
+        iw.println("private final static MLogger logger = MLog.getLogger( \042" + genclass + "\042 );");
+        iw.println();
+
+        iw.println("volatile NewPooledConnection parentPooledConnection;");
+        iw.println();
+
+        iw.println("ConnectionEventListener cel = new ConnectionEventListener()");
+        iw.println("{");
+        iw.upIndent();
+
+        iw.println("public void connectionErrorOccurred(ConnectionEvent evt)");
+        iw.println("{ /* DON'T detach()... IGNORE -- this could be an ordinary error. Leave it to the PooledConnection to test, but leave proxies intact */ }");
+        //BAD puppy -- iw.println("{ detach(); }");
+
+        iw.println();
+        iw.println("public void connectionClosed(ConnectionEvent evt)");
+        iw.println("{ detach(); }");
+
+        iw.downIndent();
+        iw.println("};");
+        iw.println();
+
+        iw.println("void attach( NewPooledConnection parentPooledConnection )");
+        iw.println("{");
+        iw.upIndent();
+        iw.println("this.parentPooledConnection = parentPooledConnection;");
+        iw.println("parentPooledConnection.addConnectionEventListener( cel );");
+        iw.downIndent();
+        iw.println("}");
+        iw.println();
+        iw.println("private void detach()");
+        iw.println("{");
+        iw.upIndent();
+
+        // factored out so we could define debug versions...
+        writeDetachBody(iw);
+
+        iw.downIndent();
+        iw.println("}");
+        iw.println();
+        iw.print( CodegenUtils.fqcnLastElement( genclass ) );
+        iw.println("( " + CodegenUtils.simpleClassName( intfcl ) + " inner, NewPooledConnection parentPooledConnection )");
+        iw.println("{");
+        iw.upIndent();
+        iw.println("this( inner );");
+        iw.println("attach( parentPooledConnection );");
+        generateExtraConstructorCode( intfcl, genclass,  iw );
+        iw.downIndent();
+        iw.println("}");
+        iw.println();
+        iw.println("boolean isDetached()");
+        iw.println("{ return (this.parentPooledConnection == null); }");
+    }
+
+    protected void writeDetachBody(IndentedWriter iw) throws IOException
+    {
+        // premature-detach-debug only
+        if (PREMATURE_DETACH_DEBUG)
+        {
+            iw.println("prematureDetachRecorder.record();");
+            iw.println("if (this.isDetached())");
+            iw.upIndent();
+            iw.println("logger.warning( prematureDetachRecorder.getDump(\042Double Detach.\042) );");
+            iw.downIndent();
+        }
+        // end premature-detach-debug only
+
+        iw.println("parentPooledConnection.removeConnectionEventListener( cel );");
+        iw.println("parentPooledConnection = null;");
+    }
+
+    protected void generateExtraImports( IndentedWriter iw ) throws IOException
+    {
+        iw.println("import java.sql.*;");
+        iw.println("import javax.sql.*;");
+        iw.println("import com.mchange.v2.log.*;");
+        iw.println("import java.lang.reflect.Method;");
+        iw.println("import com.mchange.v2.sql.SqlUtils;");
+    }
+
+    void generateExtraConstructorCode( Class intfcl, String genclass, IndentedWriter iw ) throws IOException
+    {}
+
+    public static void main( String[] argv )
+    {
+        try
+        {
+            if (argv.length != 1)
+            {
+                System.err.println("java " + JdbcProxyGenerator.class.getName() + " <source-root-directory>");
+                return;
+            }
+
+            File srcroot = new File( argv[0] );
+            if (! srcroot.exists() || !srcroot.canWrite() )
+            {
+                System.err.println(JdbcProxyGenerator.class.getName() + " -- sourceroot: " + argv[0] + " must exist and be writable");
+                return;
+            }
+
+            DelegatorGenerator mdgen = new NewProxyMetaDataGenerator();
+            DelegatorGenerator rsgen = new NewProxyResultSetGenerator();
+            DelegatorGenerator stgen = new NewProxyAnyStatementGenerator();
+            DelegatorGenerator cngen = new NewProxyConnectionGenerator();
+
+            genclass( cngen, Connection.class, "com.mchange.v2.c3p0.impl.NewProxyConnection", srcroot );
+            genclass( stgen, Statement.class, "com.mchange.v2.c3p0.impl.NewProxyStatement", srcroot );
+            genclass( stgen, PreparedStatement.class, "com.mchange.v2.c3p0.impl.NewProxyPreparedStatement", srcroot );
+            genclass( stgen, CallableStatement.class, "com.mchange.v2.c3p0.impl.NewProxyCallableStatement", srcroot );
+            genclass( rsgen, ResultSet.class, "com.mchange.v2.c3p0.impl.NewProxyResultSet", srcroot );
+            genclass( mdgen, DatabaseMetaData.class, "com.mchange.v2.c3p0.impl.NewProxyDatabaseMetaData", srcroot );
+        }
+        catch ( Exception e )
+        { e.printStackTrace(); }
+    }
+
+    static void genclass( DelegatorGenerator dg, Class intfcl, String fqcn, File srcroot ) throws IOException
+    {
+        File genDir = new File( srcroot, dirForFqcn( fqcn ) );
+        if (! genDir.exists() )
+        {
+            System.err.println( JdbcProxyGenerator.class.getName() + " -- creating directory: " + genDir.getAbsolutePath() );
+            genDir.mkdirs();
+        }
+        String fileName = CodegenUtils.fqcnLastElement( fqcn ) + ".java";
+        Writer w = null;
+        try
+        {
+            w = new BufferedWriter( new FileWriter( new File( genDir, fileName ) ) );
+            dg.writeDelegator( intfcl, fqcn, w );
+            w.flush();
+            System.err.println("Generated " + fileName);
+        }
+        finally
+        {
+            try { if (w != null) w.close(); }
+            catch ( Exception e )
+            { e.printStackTrace(); }
+        }		
+    }
+
+    static String dirForFqcn( String fqcn )
+    {
+        int last_dot = fqcn.lastIndexOf('.');
+        StringBuffer sb = new StringBuffer( fqcn.substring( 0, last_dot + 1) );
+        for (int i = 0, len = sb.length(); i < len; ++i)
+            if (sb.charAt(i) == '.')
+                sb.setCharAt(i, '/');
+        return sb.toString();
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/filter/FilterDataSource.java b/src/classes/com/mchange/v2/c3p0/filter/FilterDataSource.java
new file mode 100644
index 0000000..63919c4
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/filter/FilterDataSource.java
@@ -0,0 +1,73 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.filter;
+
+import java.io.PrintWriter;
+import java.lang.String;
+import java.sql.Connection;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+
+
+public abstract class FilterDataSource implements DataSource
+{
+    protected DataSource inner;
+
+    public FilterDataSource(DataSource inner)
+    {
+       this.inner = inner;
+    }
+
+    public Connection getConnection() throws SQLException
+    {
+        return inner.getConnection();
+    }
+
+    public Connection getConnection(String a, String b) throws SQLException
+    {
+        return inner.getConnection(a, b);
+    }
+
+    public PrintWriter getLogWriter() throws SQLException
+    {
+        return inner.getLogWriter();
+    }
+
+    public int getLoginTimeout() throws SQLException
+    {
+        return inner.getLoginTimeout();
+    }
+
+    public void setLogWriter(PrintWriter a) throws SQLException
+    {
+        inner.setLogWriter(a);
+    }
+
+    public void setLoginTimeout(int a) throws SQLException
+    {
+        inner.setLoginTimeout(a);
+    }
+
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/impl/AbstractC3P0PooledConnection.java b/src/classes/com/mchange/v2/c3p0/impl/AbstractC3P0PooledConnection.java
new file mode 100644
index 0000000..fc41fa8
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/AbstractC3P0PooledConnection.java
@@ -0,0 +1,35 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.sql.Connection;
+import javax.sql.PooledConnection;
+import com.mchange.v2.c3p0.stmt.GooGooStatementCache;
+import com.mchange.v1.util.ClosableResource;
+
+abstract class AbstractC3P0PooledConnection implements PooledConnection, ClosableResource
+{
+    abstract Connection getPhysicalConnection();
+    abstract void initStatementCache(GooGooStatementCache scache);
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/impl/AbstractIdentityTokenized.java b/src/classes/com/mchange/v2/c3p0/impl/AbstractIdentityTokenized.java
new file mode 100644
index 0000000..e1d669b
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/AbstractIdentityTokenized.java
@@ -0,0 +1,47 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+/*
+ * It would be convenient to put the getter/setter methods
+ * for the identity token here, but unfortunately we have no
+ * way of setting up the for Referenceability in multiple
+ * levels of a class hierarchy. So we leave the getters/setters,
+ * and variable initialization to code-generators.
+ */
+public abstract class AbstractIdentityTokenized implements IdentityTokenized
+{
+    public boolean equals(Object o)
+    {
+	if (this == o)
+	    return true;
+
+	if (o instanceof IdentityTokenized)
+	    return this.getIdentityToken().equals( ((IdentityTokenized) o).getIdentityToken() );
+	else
+	    return false;
+    }
+
+    public int hashCode()
+    { return ~this.getIdentityToken().hashCode(); }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/impl/AbstractPoolBackedDataSource.java b/src/classes/com/mchange/v2/c3p0/impl/AbstractPoolBackedDataSource.java
new file mode 100644
index 0000000..423a4e4
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/AbstractPoolBackedDataSource.java
@@ -0,0 +1,500 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.io.*;
+import java.sql.*;
+
+import javax.sql.*;
+
+import com.mchange.lang.ThrowableUtils;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v2.log.*;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+import java.beans.PropertyChangeListener;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import com.mchange.v2.c3p0.cfg.C3P0Config;
+
+public abstract class AbstractPoolBackedDataSource extends PoolBackedDataSourceBase implements PooledDataSource
+{
+    final static MLogger logger = MLog.getLogger( AbstractPoolBackedDataSource.class );
+
+    final static String NO_CPDS_ERR_MSG =
+        "Attempted to use an uninitialized PoolBackedDataSource. " +
+        "Please call setConnectionPoolDataSource( ... ) to initialize.";
+
+    //MT: protected by this' lock
+    transient C3P0PooledConnectionPoolManager poolManager;
+    transient boolean is_closed = false;
+    //MT: end protected by this' lock
+
+    protected AbstractPoolBackedDataSource( boolean autoregister )
+    {
+        super( autoregister );
+        setUpPropertyEvents();
+    }
+
+    protected AbstractPoolBackedDataSource(String configName)
+    { 
+        this( true );
+        initializeNamedConfig( configName );
+    }
+
+    private void setUpPropertyEvents()
+    {
+        PropertyChangeListener l = new PropertyChangeListener()
+        {
+            public void propertyChange( PropertyChangeEvent evt )
+            { resetPoolManager(); }
+        };
+        this.addPropertyChangeListener( l );
+    }
+
+
+    protected void initializeNamedConfig(String configName)
+    {
+        try
+        {
+            if (configName != null)
+            {
+                C3P0Config.bindNamedConfigToBean( this, configName ); 
+                if ( this.getDataSourceName().equals( this.getIdentityToken() ) ) //dataSourceName has not been specified in config
+                    this.setDataSourceName( configName );
+            }
+        }
+        catch (Exception e)
+        {
+            if (logger.isLoggable( MLevel.WARNING ))
+                logger.log( MLevel.WARNING, 
+                        "Error binding PoolBackedDataSource to named-config '" + configName + 
+                        "'. Some default-config values may be used.", 
+                        e);
+        }
+    }
+
+//  Commented out method is just super.getReference() with a lot of extra printing
+
+//  public javax.naming.Reference getReference() throws javax.naming.NamingException
+//  {
+//  System.err.println("getReference()!!!!");
+//  new Exception("PRINT-STACK-TRACE").printStackTrace();
+//  javax.naming.Reference out = super.getReference();
+//  System.err.println(out);
+//  return out;
+//  }
+
+    // report our ID token as dataSourceName if we have no
+    // name explicitly set
+    public String getDataSourceName()
+    {
+ 	String out = super.getDataSourceName();
+ 	if (out == null)
+ 	    out = this.getIdentityToken();
+ 	return out;
+    }
+
+    //implementation of javax.sql.DataSource
+    public Connection getConnection() throws SQLException
+    {
+        PooledConnection pc = getPoolManager().getPool().checkoutPooledConnection();
+        return pc.getConnection();
+    }
+
+    public Connection getConnection(String username, String password) throws SQLException
+    { 
+        PooledConnection pc = getPoolManager().getPool(username, password).checkoutPooledConnection();
+        return pc.getConnection();
+    }
+
+    public PrintWriter getLogWriter() throws SQLException
+    { return assertCpds().getLogWriter(); }
+
+    public void setLogWriter(PrintWriter out) throws SQLException
+    { assertCpds().setLogWriter( out ); }
+
+    public int getLoginTimeout() throws SQLException
+    { return assertCpds().getLoginTimeout(); }
+
+    public void setLoginTimeout(int seconds) throws SQLException
+    { assertCpds().setLoginTimeout( seconds ); }
+
+    //implementation of com.mchange.v2.c3p0.PoolingDataSource
+    public int getNumConnections() throws SQLException
+    { return getPoolManager().getPool().getNumConnections(); }
+
+    public int getNumIdleConnections() throws SQLException
+    { return getPoolManager().getPool().getNumIdleConnections(); }
+
+    public int getNumBusyConnections() throws SQLException
+    { return getPoolManager().getPool().getNumBusyConnections(); }
+
+    public int getNumUnclosedOrphanedConnections() throws SQLException
+    { return getPoolManager().getPool().getNumUnclosedOrphanedConnections(); }
+
+    public int getNumConnectionsDefaultUser() throws SQLException
+    { return getNumConnections();}
+
+    public int getNumIdleConnectionsDefaultUser() throws SQLException
+    { return getNumIdleConnections();}
+
+    public int getNumBusyConnectionsDefaultUser() throws SQLException
+    { return getNumBusyConnections();}
+
+    public int getNumUnclosedOrphanedConnectionsDefaultUser() throws SQLException
+    { return getNumUnclosedOrphanedConnections();}
+
+    public int getStatementCacheNumStatementsDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getStatementCacheNumStatements(); }
+
+    public int getStatementCacheNumCheckedOutDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getStatementCacheNumCheckedOut(); }
+
+    public int getStatementCacheNumConnectionsWithCachedStatementsDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getStatementCacheNumConnectionsWithCachedStatements(); }
+
+    public float getEffectivePropertyCycleDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getEffectivePropertyCycle(); }
+    
+    public long getStartTimeMillisDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getStartTime(); }
+
+    public long getUpTimeMillisDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getUpTime(); }
+    
+    public long getNumFailedCheckinsDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getNumFailedCheckins(); }
+
+    public long getNumFailedCheckoutsDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getNumFailedCheckouts(); }
+
+    public long getNumFailedIdleTestsDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getNumFailedIdleTests(); }
+
+    public int getNumThreadsAwaitingCheckoutDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getNumThreadsAwaitingCheckout(); }
+
+    public int getThreadPoolSize() throws SQLException
+    { return getPoolManager().getThreadPoolSize(); }
+
+    public int getThreadPoolNumActiveThreads() throws SQLException
+    { return getPoolManager().getThreadPoolNumActiveThreads(); }
+
+    public int getThreadPoolNumIdleThreads() throws SQLException
+    { return getPoolManager().getThreadPoolNumIdleThreads(); }
+
+    public int getThreadPoolNumTasksPending() throws SQLException
+    { return getPoolManager().getThreadPoolNumTasksPending(); }
+
+    public String sampleThreadPoolStackTraces() throws SQLException
+    { return getPoolManager().getThreadPoolStackTraces(); }
+
+    public String sampleThreadPoolStatus() throws SQLException
+    { return getPoolManager().getThreadPoolStatus(); }
+
+    public String sampleStatementCacheStatusDefaultUser() throws SQLException
+    { return getPoolManager().getPool().dumpStatementCacheStatus(); }
+    
+    public String sampleStatementCacheStatus(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).dumpStatementCacheStatus(); }
+    
+    public Throwable getLastAcquisitionFailureDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getLastAcquisitionFailure(); }
+
+    public Throwable getLastCheckinFailureDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getLastCheckinFailure(); }
+
+    public Throwable getLastCheckoutFailureDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getLastCheckoutFailure(); }
+
+    public Throwable getLastIdleTestFailureDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getLastIdleTestFailure(); }
+
+    public Throwable getLastConnectionTestFailureDefaultUser() throws SQLException
+    { return getPoolManager().getPool().getLastConnectionTestFailure(); }
+    
+    public Throwable getLastAcquisitionFailure(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getLastAcquisitionFailure(); }
+
+    public Throwable getLastCheckinFailure(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getLastCheckinFailure(); }
+
+    public Throwable getLastCheckoutFailure(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getLastCheckoutFailure(); }
+
+    public Throwable getLastIdleTestFailure(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getLastIdleTestFailure(); }
+
+    public Throwable getLastConnectionTestFailure(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getLastConnectionTestFailure(); }
+    
+    public int getNumThreadsAwaitingCheckout(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getNumThreadsAwaitingCheckout(); }
+
+    public String sampleLastAcquisitionFailureStackTraceDefaultUser() throws SQLException
+    { 
+        Throwable t = getLastAcquisitionFailureDefaultUser(); 
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+    
+    public String sampleLastCheckinFailureStackTraceDefaultUser() throws SQLException
+    { 
+        Throwable t = getLastCheckinFailureDefaultUser(); 
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+    
+    public String sampleLastCheckoutFailureStackTraceDefaultUser() throws SQLException
+    { 
+        Throwable t = getLastCheckoutFailureDefaultUser(); 
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+
+    public String sampleLastIdleTestFailureStackTraceDefaultUser() throws SQLException
+    { 
+        Throwable t = getLastIdleTestFailureDefaultUser(); 
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+
+    public String sampleLastConnectionTestFailureStackTraceDefaultUser() throws SQLException
+    { 
+        Throwable t = getLastConnectionTestFailureDefaultUser();
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+    
+    public String sampleLastAcquisitionFailureStackTrace(String username, String password) throws SQLException
+    { 
+        Throwable t = getLastAcquisitionFailure(username, password); 
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+
+    public String sampleLastCheckinFailureStackTrace(String username, String password) throws SQLException
+    { 
+        Throwable t = getLastCheckinFailure(username, password); 
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+
+    public String sampleLastCheckoutFailureStackTrace(String username, String password) throws SQLException
+    { 
+        Throwable t = getLastCheckoutFailure(username, password); 
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+
+    public String sampleLastIdleTestFailureStackTrace(String username, String password) throws SQLException
+    { 
+        Throwable t = getLastIdleTestFailure(username, password); 
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+
+    public String sampleLastConnectionTestFailureStackTrace(String username, String password) throws SQLException
+    { 
+        Throwable t = getLastConnectionTestFailure(username, password); 
+        return t == null ? null : ThrowableUtils.extractStackTrace( t ); 
+    }
+
+    public void softResetDefaultUser() throws SQLException
+    { getPoolManager().getPool().reset(); }
+
+    public int getNumConnections(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getNumConnections(); }
+
+    public int getNumIdleConnections(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getNumIdleConnections(); }
+
+    public int getNumBusyConnections(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getNumBusyConnections(); }
+
+    public int getNumUnclosedOrphanedConnections(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getNumUnclosedOrphanedConnections(); }
+
+    public int getStatementCacheNumStatements(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getStatementCacheNumStatements(); }
+
+    public int getStatementCacheNumCheckedOut(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getStatementCacheNumCheckedOut(); }
+
+    public int getStatementCacheNumConnectionsWithCachedStatements(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getStatementCacheNumConnectionsWithCachedStatements(); }
+
+    public float getEffectivePropertyCycle(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getEffectivePropertyCycle(); }
+
+    public long getStartTimeMillis(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getStartTime(); }
+
+    public long getUpTimeMillis(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getUpTime(); }
+    
+    public long getNumFailedCheckins(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getNumFailedCheckins(); }
+
+    public long getNumFailedCheckouts(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getNumFailedCheckouts(); }
+
+    public long getNumFailedIdleTests(String username, String password) throws SQLException
+    { return assertAuthPool(username, password).getNumFailedIdleTests(); }
+
+    public void softReset(String username, String password) throws SQLException
+    { assertAuthPool(username, password).reset(); }
+
+    public int getNumBusyConnectionsAllUsers() throws SQLException
+    { return getPoolManager().getNumBusyConnectionsAllAuths(); }
+
+    public int getNumIdleConnectionsAllUsers() throws SQLException
+    { return getPoolManager().getNumIdleConnectionsAllAuths(); }
+
+    public int getNumConnectionsAllUsers() throws SQLException
+    { return getPoolManager().getNumConnectionsAllAuths(); }
+
+    public int getNumUnclosedOrphanedConnectionsAllUsers() throws SQLException
+    { return getPoolManager().getNumUnclosedOrphanedConnectionsAllAuths(); }
+
+    public int getStatementCacheNumStatementsAllUsers() throws SQLException
+    { return getPoolManager().getStatementCacheNumStatementsAllUsers(); }
+
+    public int getStatementCacheNumCheckedOutStatementsAllUsers() throws SQLException
+    { return getPoolManager().getStatementCacheNumCheckedOutStatementsAllUsers(); }
+
+    public synchronized int getStatementCacheNumConnectionsWithCachedStatementsAllUsers() throws SQLException
+    { return getPoolManager().getStatementCacheNumConnectionsWithCachedStatementsAllUsers(); }
+
+    public void softResetAllUsers() throws SQLException
+    { getPoolManager().softResetAllAuths(); }
+
+    public int getNumUserPools() throws SQLException
+    { return getPoolManager().getNumManagedAuths(); }
+
+    public Collection getAllUsers() throws SQLException
+    {
+        LinkedList out = new LinkedList();
+        Set auths = getPoolManager().getManagedAuths();
+        for ( Iterator ii = auths.iterator(); ii.hasNext(); )
+            out.add( ((DbAuth) ii.next()).getUser() );
+        return Collections.unmodifiableList( out );
+    }
+
+    public synchronized void hardReset()
+    {
+        resetPoolManager(); 
+    }
+
+    public synchronized void close()
+    { 
+        resetPoolManager(); 
+        is_closed = true;
+        
+        C3P0Registry.markClosed(this);
+
+        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable(MLevel.FINEST))
+        {
+            logger.log(MLevel.FINEST, 
+                    this.getClass().getName() + '@' + Integer.toHexString( System.identityHashCode( this ) ) +
+                    " has been closed. ",
+                    new Exception("DEBUG STACK TRACE for PoolBackedDataSource.close()."));
+        }
+    }
+
+    /**
+     * @deprecated the force_destroy argument is now meaningless, as pools are no longer
+     *             potentially shared between multiple DataSources.
+     */
+    public void close(boolean force_destroy)
+    { close(); }
+
+    //other code
+    public synchronized void resetPoolManager() //used by other, wrapping datasources in package, and in mbean package
+    { resetPoolManager( true ); }
+
+    public synchronized void resetPoolManager( boolean close_checked_out_connections ) //used by other, wrapping datasources in package, and in mbean package
+    {
+        if ( poolManager != null )
+        {
+            poolManager.close( close_checked_out_connections );
+            poolManager = null;
+        }
+    }
+
+    private synchronized ConnectionPoolDataSource assertCpds() throws SQLException
+    {
+        if ( is_closed )
+            throw new SQLException(this + " has been closed() -- you can no longer use it.");
+
+        ConnectionPoolDataSource out = this.getConnectionPoolDataSource();
+        if ( out == null )
+            throw new SQLException(NO_CPDS_ERR_MSG);
+        return out;
+    }
+
+    private synchronized C3P0PooledConnectionPoolManager getPoolManager() throws SQLException
+    {
+        if (poolManager == null)
+        {
+            ConnectionPoolDataSource cpds = assertCpds();
+            poolManager = new C3P0PooledConnectionPoolManager(cpds, null, null, this.getNumHelperThreads(), this.getIdentityToken());
+            if (logger.isLoggable(MLevel.INFO))
+                logger.info("Initializing c3p0 pool... " + this.toString()  /* + "; using pool manager: " + poolManager */);
+        }
+        return poolManager;	    
+    }
+
+    private C3P0PooledConnectionPool assertAuthPool( String username, String password ) throws SQLException
+    {
+	C3P0PooledConnectionPool authPool = getPoolManager().getPool(username, password, false);
+	if (authPool == null)
+	    throw new SQLException("No pool has been yet been established for Connections authenticated by user '" +
+				   username + "' with the password provided. [Use getConnection( username, password ) " +
+				   "to initialize such a pool.]");
+	else
+	    return authPool;
+    }
+
+    // serialization stuff -- set up bound/constrained property event handlers on deserialization
+    private static final long serialVersionUID = 1;
+    private static final short VERSION = 0x0001;
+
+    private void writeObject( ObjectOutputStream oos ) throws IOException
+    {
+        oos.writeShort( VERSION );
+    }
+
+    private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
+    {
+        short version = ois.readShort();
+        switch (version)
+        {
+        case VERSION:
+            setUpPropertyEvents();
+            break;
+        default:
+            throw new IOException("Unsupported Serialized Version: " + version);
+        }
+    }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/impl/AuthMaskingProperties.java b/src/classes/com/mchange/v2/c3p0/impl/AuthMaskingProperties.java
new file mode 100644
index 0000000..5f795ab
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/AuthMaskingProperties.java
@@ -0,0 +1,67 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+public class AuthMaskingProperties extends Properties
+{
+    public AuthMaskingProperties()
+    { super(); }
+
+    public AuthMaskingProperties( Properties p )
+    { super( p ); }
+
+    public static AuthMaskingProperties fromAnyProperties( Properties p )
+    { 
+	AuthMaskingProperties out = new AuthMaskingProperties();
+	for( Enumeration e = p.propertyNames(); e.hasMoreElements(); )
+	    {
+		String key = (String) e.nextElement();
+		out.setProperty( key, p.getProperty( key ) );
+	    }
+	return out;
+    }
+
+    private String normalToString()
+    { return super.toString(); }
+
+    public String toString()
+    {
+	boolean hasUser = (this.get("user") != null);
+	boolean hasPassword = (this.get("password") != null);
+	if ( hasUser || hasPassword )
+	    {
+		AuthMaskingProperties clone = (AuthMaskingProperties) this.clone();
+		if (hasUser)
+		    clone.put("user", "******");
+		if (hasPassword)
+		    clone.put("password", "******");
+		return clone.normalToString();
+	    }
+	else
+	    return this.normalToString();
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/impl/C3P0Defaults.java b/src/classes/com/mchange/v2/c3p0/impl/C3P0Defaults.java
new file mode 100644
index 0000000..9358b44
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/C3P0Defaults.java
@@ -0,0 +1,207 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.lang.reflect.*;
+import java.util.*;
+import com.mchange.v2.c3p0.ConnectionTester;
+
+// all public static methods should have the name of a c3p0 config property and
+// return its default value
+public final class C3P0Defaults
+{
+    private final static int MAX_STATEMENTS                   = 0;
+    private final static int MAX_STATEMENTS_PER_CONNECTION    = 0;
+    private final static int INITIAL_POOL_SIZE                = 3;  
+    private final static int MIN_POOL_SIZE                    = 3;
+    private final static int MAX_POOL_SIZE                    = 15;
+    private final static int IDLE_CONNECTION_TEST_PERIOD      = 0;  //idle connections never tested
+    private final static int MAX_IDLE_TIME                    = 0;  //seconds, 0 means connections never expire
+    private final static int PROPERTY_CYCLE                   = 0;  //seconds
+    private final static int ACQUIRE_INCREMENT                = 3;
+    private final static int ACQUIRE_RETRY_ATTEMPTS           = 30;
+    private final static int ACQUIRE_RETRY_DELAY              = 1000; //milliseconds
+    private final static int CHECKOUT_TIMEOUT                 = 0;    //milliseconds
+    private final static int MAX_ADMINISTRATIVE_TASK_TIME     = 0;    //seconds
+    private final static int MAX_IDLE_TIME_EXCESS_CONNECTIONS = 0;    //seconds
+    private final static int MAX_CONNECTION_AGE               = 0;    //seconds
+    private final static int UNRETURNED_CONNECTION_TIMEOUT    = 0;    //seconds
+
+    private final static boolean BREAK_AFTER_ACQUIRE_FAILURE                 = false;
+    private final static boolean TEST_CONNECTION_ON_CHECKOUT                 = false;
+    private final static boolean TEST_CONNECTION_ON_CHECKIN                  = false;
+    private final static boolean AUTO_COMMIT_ON_CLOSE                        = false;
+    private final static boolean FORCE_IGNORE_UNRESOLVED_TXNS                = false;
+    private final static boolean USES_TRADITIONAL_REFLECTIVE_PROXIES         = false;
+    private final static boolean DEBUG_UNRETURNED_CONNECTION_STACK_TRACES    = false;
+
+    private final static ConnectionTester CONNECTION_TESTER = new DefaultConnectionTester();
+
+    private final static int NUM_HELPER_THREADS = 3;
+
+    private final static String AUTOMATIC_TEST_TABLE             = null;
+    private final static String CONNECTION_CUSTOMIZER_CLASS_NAME = null;
+    private final static String DRIVER_CLASS                     = null;
+    private final static String JDBC_URL                         = null;
+    private final static String OVERRIDE_DEFAULT_USER            = null;
+    private final static String OVERRIDE_DEFAULT_PASSWORD        = null;
+    private final static String PASSWORD                         = null;
+    private final static String PREFERRED_TEST_QUERY             = null;
+    private final static String FACTORY_CLASS_LOCATION           = null;
+    private final static String USER_OVERRIDES_AS_STRING         = null;
+    private final static String USER                             = null;
+
+    private static Set KNOWN_PROPERTIES;
+
+    static
+    {
+	Method[] methods = C3P0Defaults.class.getMethods();
+	Set s = new HashSet();
+	for (int i = 0, len = methods.length; i < len; ++i)
+	    {
+		Method m = methods[i];
+		if (Modifier.isStatic( m.getModifiers() ) && m.getParameterTypes().length == 0)
+		    s.add( m.getName() );
+	    }
+	KNOWN_PROPERTIES = Collections.unmodifiableSet( s );
+    }
+
+    public static Set getKnownProperties()
+    { return KNOWN_PROPERTIES; }
+
+    public static boolean isKnownProperty( String s )
+    { return KNOWN_PROPERTIES.contains( s ); }
+
+    public static int maxStatements()
+    { return MAX_STATEMENTS; }
+
+    public static int maxStatementsPerConnection()
+    { return MAX_STATEMENTS_PER_CONNECTION; }
+
+    public static int initialPoolSize()
+    { return INITIAL_POOL_SIZE; }
+
+    public static int minPoolSize()
+    { return MIN_POOL_SIZE; }
+
+    public static int maxPoolSize()
+    { return MAX_POOL_SIZE; }
+
+    public static int idleConnectionTestPeriod()
+    { return IDLE_CONNECTION_TEST_PERIOD; }
+
+    public static int maxIdleTime()
+    { return MAX_IDLE_TIME; }
+
+    public static int unreturnedConnectionTimeout()
+    { return UNRETURNED_CONNECTION_TIMEOUT; }
+
+    public static int propertyCycle()
+    { return PROPERTY_CYCLE; }
+
+    public static int acquireIncrement()
+    { return ACQUIRE_INCREMENT; }
+
+    public static int acquireRetryAttempts()
+    { return ACQUIRE_RETRY_ATTEMPTS; }
+
+    public static int acquireRetryDelay()
+    { return ACQUIRE_RETRY_DELAY; }
+
+    public static int checkoutTimeout()
+    { return CHECKOUT_TIMEOUT; }
+
+    public static String connectionCustomizerClassName()
+    { return CONNECTION_CUSTOMIZER_CLASS_NAME; }
+
+    public static ConnectionTester connectionTester()
+    { return CONNECTION_TESTER; }
+
+    public static String connectionTesterClassName()
+    { return CONNECTION_TESTER.getClass().getName(); }
+
+    public static String automaticTestTable()
+    { return AUTOMATIC_TEST_TABLE; }
+
+    public static String driverClass()
+    { return DRIVER_CLASS; }
+
+    public static String jdbcUrl()
+    { return JDBC_URL; }
+
+    public static int numHelperThreads()
+    { return NUM_HELPER_THREADS; }
+
+    public static boolean breakAfterAcquireFailure()
+    { return BREAK_AFTER_ACQUIRE_FAILURE; }
+
+    public static boolean testConnectionOnCheckout()
+    { return TEST_CONNECTION_ON_CHECKOUT; }
+
+    public static boolean testConnectionOnCheckin()
+    { return TEST_CONNECTION_ON_CHECKIN; }
+
+    public static boolean autoCommitOnClose()
+    { return AUTO_COMMIT_ON_CLOSE; }
+
+    public static boolean forceIgnoreUnresolvedTransactions()
+    { return FORCE_IGNORE_UNRESOLVED_TXNS; }
+
+    public static boolean debugUnreturnedConnectionStackTraces()
+    { return DEBUG_UNRETURNED_CONNECTION_STACK_TRACES; }
+
+    public static boolean usesTraditionalReflectiveProxies()
+    { return USES_TRADITIONAL_REFLECTIVE_PROXIES; }
+
+    public static String preferredTestQuery()
+    { return PREFERRED_TEST_QUERY; }
+
+    public static String userOverridesAsString()
+    { return USER_OVERRIDES_AS_STRING; }
+
+    public static String factoryClassLocation()
+    { return FACTORY_CLASS_LOCATION; }
+
+    public static String overrideDefaultUser()
+    { return OVERRIDE_DEFAULT_USER; }
+
+    public static String overrideDefaultPassword()
+    { return OVERRIDE_DEFAULT_PASSWORD; }
+
+    public static String user()
+    { return USER; }
+
+    public static String password()
+    { return PASSWORD; }
+
+    public static int maxAdministrativeTaskTime()
+    { return MAX_ADMINISTRATIVE_TASK_TIME; }
+
+    public static int maxIdleTimeExcessConnections()
+    { return MAX_IDLE_TIME_EXCESS_CONNECTIONS; }
+
+    public static int maxConnectionAge()
+    { return MAX_CONNECTION_AGE; }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/impl/C3P0ImplUtils.java b/src/classes/com/mchange/v2/c3p0/impl/C3P0ImplUtils.java
new file mode 100644
index 0000000..459e6be
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/C3P0ImplUtils.java
@@ -0,0 +1,381 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.beans.*;
+import java.util.*;
+import java.lang.reflect.*;
+import java.net.InetAddress;
+
+import com.mchange.v2.c3p0.*;
+import com.mchange.v2.cfg.MultiPropertiesConfig;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.sql.Connection;
+import java.sql.SQLException;
+import com.mchange.lang.ByteUtils;
+import com.mchange.v2.encounter.EncounterCounter;
+import com.mchange.v2.encounter.EqualityEncounterCounter;
+import com.mchange.v2.lang.VersionUtils;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import com.mchange.v2.ser.SerializableUtils;
+import com.mchange.v2.sql.SqlUtils;
+
+public final class C3P0ImplUtils
+{
+    // turning this on would only test to generate long tokens
+    // on 64 bit machines, but since identityHashCode() is not
+    // GUARANTEED unique under 32-bit JVMs, even if in practice
+    // always is, we always test to be sure we're not reusing
+    // an identity token.
+    private final static boolean CONDITIONAL_LONG_TOKENS = false;
+
+    final static MLogger logger = MLog.getLogger( C3P0ImplUtils.class );
+
+    public final static DbAuth NULL_AUTH = new DbAuth(null,null);
+
+    public final static Object[] NOARGS = new Object[0]; 
+
+    private final static EncounterCounter ID_TOKEN_COUNTER;
+
+    static
+    {
+	if (CONDITIONAL_LONG_TOKENS)
+	    {
+		boolean long_tokens;
+		Integer jnb = VersionUtils.jvmNumberOfBits();
+		if (jnb == null)
+		    long_tokens = true;
+		else if (jnb.intValue() > 32)
+		    long_tokens = true;
+		else
+		    long_tokens = false;
+		
+		if (long_tokens)
+		    ID_TOKEN_COUNTER = new EqualityEncounterCounter();
+		else
+		    ID_TOKEN_COUNTER = null;
+	    }
+	else
+	    ID_TOKEN_COUNTER = new EqualityEncounterCounter();
+     }
+    
+    public final static String VMID_PROPKEY = "com.mchange.v2.c3p0.VMID";
+    private final static String VMID_PFX;
+    
+    static
+    {
+        String vmid = MultiPropertiesConfig.readVmConfig().getProperty(VMID_PROPKEY);
+        if (vmid == null || (vmid = vmid.trim()).equals("") || vmid.equals("AUTO"))
+            VMID_PFX = generateVmId() + '|';
+        else if (vmid.equals("NONE"))
+            VMID_PFX = "";
+        else
+            VMID_PFX = vmid + "|";
+    }
+
+    //MT: protected by class' lock
+    static String connectionTesterClassName = null;
+    static ConnectionTester cachedTester = null;
+    
+    private static String generateVmId()
+    {
+        DataOutputStream dos = null;
+        DataInputStream  dis = null;
+        try
+        {
+            SecureRandom srand = new SecureRandom();
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            dos = new DataOutputStream( baos );
+            try
+            {
+                dos.write( InetAddress.getLocalHost().getAddress() );
+            }
+            catch (Exception e)
+            {
+                if (logger.isLoggable(MLevel.INFO))
+                    logger.log(MLevel.INFO, "Failed to get local InetAddress for VMID. This is unlikely to matter. At all. We'll add some extra randomness", e);
+                dos.write( srand.nextInt() );
+            }
+            dos.writeLong(System.currentTimeMillis());
+            dos.write( srand.nextInt() );
+            
+            int remainder = baos.size() % 4; //if it wasn't a 4 byte inet address
+            if (remainder > 0)
+            {
+                int pad = 4 - remainder;
+                byte[] pad_bytes = new byte[pad];
+                srand.nextBytes(pad_bytes);
+                dos.write(pad_bytes);
+            }
+            
+            StringBuffer sb = new StringBuffer(32);
+            byte[] vmid_bytes = baos.toByteArray();
+            dis = new DataInputStream(new ByteArrayInputStream( vmid_bytes ) );
+            for (int i = 0, num_ints = vmid_bytes.length / 4; i < num_ints; ++i)
+            {
+                int signed = dis.readInt();
+                long unsigned = ((long) signed) & 0x00000000FFFFFFFFL; 
+                sb.append(Long.toString(unsigned, Character.MAX_RADIX));
+            }
+            return sb.toString();
+        }
+        catch (IOException e)
+        {
+            if (logger.isLoggable(MLevel.WARNING))
+                logger.log(MLevel.WARNING, 
+                           "Bizarro! IOException while reading/writing from ByteArray-based streams? " +
+                           "We're skipping the VMID thing. It almost certainly doesn't matter, " +
+                           "but please report the error.", 
+                           e);
+            return "";
+        }
+        finally
+        {
+            // this is like total overkill for byte-array based streams,
+            // but it's a good habit
+            try { if (dos != null) dos.close(); }
+            catch ( IOException e )
+            { logger.log(MLevel.WARNING, "Huh? Exception close()ing a byte-array bound OutputStream.", e); }
+            try { if (dis != null) dis.close(); }
+            catch ( IOException e )
+            { logger.log(MLevel.WARNING, "Huh? Exception close()ing a byte-array bound IntputStream.", e); }
+        }
+    }
+
+    // identityHashCode() is not a sufficient unique token, because they are
+    // not guaranteed unique, and in practice are occasionally not unique,
+    // particularly on 64-bit systems.
+
+    public static String allocateIdentityToken(Object o)
+    { 
+	if (o == null)
+	    return null;
+	else
+	    {
+		String shortIdToken = Integer.toString( System.identityHashCode( o ), 16 );
+
+		//new Exception( "DEBUG_STACK_TRACE: " + o.getClass().getName() + " " + shortIdToken ).printStackTrace();
+
+		String out;
+		long count;
+        StringBuffer sb = new StringBuffer(128);
+        sb.append(VMID_PFX);
+		if (ID_TOKEN_COUNTER != null && ((count = ID_TOKEN_COUNTER.encounter( shortIdToken )) > 0))
+		    {
+			sb.append( shortIdToken );
+			sb.append('#');
+			sb.append( count );
+		    }
+		else
+            sb.append(shortIdToken);
+
+		out = sb.toString().intern();
+
+		return out;
+	    }
+    }
+
+    public static DbAuth findAuth(Object o)
+	throws SQLException
+    {
+	if ( o == null )
+	    return NULL_AUTH;
+
+	String user = null;
+	String password = null;
+
+	String overrideDefaultUser    = null;
+	String overrideDefaultPassword = null;
+
+	try
+	    {
+		BeanInfo bi = Introspector.getBeanInfo( o.getClass() );
+		PropertyDescriptor[] pds = bi.getPropertyDescriptors();
+		for (int i = 0, len = pds.length; i < len; ++i)
+		    {
+			PropertyDescriptor pd = pds[i];
+			Class propCl = pd.getPropertyType();
+			String propName = pd.getName();
+			if (propCl == String.class)
+			    {
+//  				System.err.println( "---> " + propName );
+//  				System.err.println( o.getClass() );
+//  				System.err.println( pd.getReadMethod() );
+
+				Method readMethod = pd.getReadMethod();
+				if (readMethod != null)
+				    {
+					Object propVal = readMethod.invoke( o, NOARGS );
+					String value = (String) propVal;
+					if ("user".equals(propName))
+					    user = value;
+					else if ("password".equals(propName))
+					    password = value;
+					else if ("overrideDefaultUser".equals(propName))
+					    overrideDefaultUser = value;
+					else if ("overrideDefaultPassword".equals(propName))
+					    overrideDefaultPassword = value;
+				    }
+			    }
+		    }
+		if (overrideDefaultUser != null)
+		    return new DbAuth( overrideDefaultUser, overrideDefaultPassword );
+		else if (user != null)
+		    return new DbAuth( user, password );
+		else
+		    return NULL_AUTH;
+	    }
+	catch (Exception e)
+	    {
+		if (Debug.DEBUG && logger.isLoggable( MLevel.FINE ))
+		    logger.log( MLevel.FINE, "An exception occurred while trying to extract the default authentification info from a bean.", e );
+		throw SqlUtils.toSQLException(e);
+	    }
+    }
+
+    static void resetTxnState( Connection pCon, 
+			       boolean forceIgnoreUnresolvedTransactions, 
+			       boolean autoCommitOnClose, 
+			       boolean txnKnownResolved ) throws SQLException
+    {
+	if ( !forceIgnoreUnresolvedTransactions && !pCon.getAutoCommit() )
+	    {
+		if (! autoCommitOnClose && ! txnKnownResolved)
+		    {
+			//System.err.println("Rolling back potentially unresolved txn...");
+			pCon.rollback();
+		    }	
+		pCon.setAutoCommit( true ); //implies commit if not already rolled back.
+	    }
+    }
+
+    public synchronized static ConnectionTester defaultConnectionTester()
+    {
+	String dfltCxnTesterClassName = PoolConfig.defaultConnectionTesterClassName();
+	if ( connectionTesterClassName != null && connectionTesterClassName.equals(dfltCxnTesterClassName) )
+	    return cachedTester;
+	else
+	    {
+		try 
+		    { 
+			cachedTester = (ConnectionTester) Class.forName( dfltCxnTesterClassName ).newInstance(); 
+			connectionTesterClassName = cachedTester.getClass().getName();
+		    }
+		catch ( Exception e )
+		    {
+			//e.printStackTrace();
+			if ( logger.isLoggable( MLevel.WARNING ) )
+			    logger.log(MLevel.WARNING, 
+				       "Could not load ConnectionTester " + dfltCxnTesterClassName + ", using built in default.", 
+				       e);
+			cachedTester = C3P0Defaults.connectionTester();
+			connectionTesterClassName = cachedTester.getClass().getName();
+		    }
+		return cachedTester;
+	    }
+    }
+
+    public static boolean supportsMethod(Object target, String mname, Class[] argTypes)
+    {
+	try {return (target.getClass().getMethod( mname, argTypes ) != null); }
+	catch ( NoSuchMethodException e )
+	    { return false; }
+	catch (SecurityException e)
+	    {
+		if ( logger.isLoggable( MLevel.FINE ) )
+		    logger.log(MLevel.FINE, 
+			       "We were denied access in a check of whether " + target + " supports method " + mname + 
+			       ". Prob means external clients have no access, returning false.",
+			       e);
+		return false;
+	    }
+    }
+
+    private final static String HASM_HEADER = "HexAsciiSerializedMap";
+
+    public static String createUserOverridesAsString( Map userOverrides ) throws IOException
+    {
+	StringBuffer sb = new StringBuffer();
+	sb.append(HASM_HEADER);
+	sb.append('[');
+	sb.append( ByteUtils.toHexAscii( SerializableUtils.toByteArray( userOverrides ) ) );
+	sb.append(']');
+	return sb.toString();
+    }
+
+    public static Map parseUserOverridesAsString( String userOverridesAsString ) throws IOException, ClassNotFoundException
+    { 
+	if (userOverridesAsString != null)
+	    {
+		String hexAscii = userOverridesAsString.substring(HASM_HEADER.length() + 1, userOverridesAsString.length() - 1);
+		byte[] serBytes = ByteUtils.fromHexAscii( hexAscii );
+		return Collections.unmodifiableMap( (Map) SerializableUtils.fromByteArray( serBytes ) );
+	    }
+	else
+	    return Collections.EMPTY_MAP;
+    }
+
+    private C3P0ImplUtils()
+    {}
+}
+
+
+
+//  Class methodClass = readMethod.getDeclaringClass();
+//  Package methodPkg = methodClass.getPackage();
+//  System.err.println( methodPkg.getName() + '\t' + C3P0ImplUtils.class.getPackage().getName() );
+//  if (! methodPkg.getName().equals( 
+//  				 C3P0ImplUtils.class.getPackage().getName() ) )
+//  {
+//      System.err.println("public check: " + (methodClass.getModifiers() & Modifier.PUBLIC));
+//      if ((methodClass.getModifiers() & Modifier.PUBLIC) == 0)
+//  	{
+//  	    System.err.println("SKIPPED -- Can't Access!");
+//  	    continue;
+//  	}
+//  }
+//  System.err.println( o );
+
+    /*
+    private final static ThreadLocal threadLocalConnectionCustomizer = new ThreadLocal();
+
+    // used so that C3P0PooledConnectionPool can pass a ConnectionCustomizer 
+    // to WrapperConnectionPoolDataSource without altering that class' public API
+    public static void setThreadConnectionCustomizer(ConnectionCustomizer cc)
+    { threadLocalConnectionCustomizer.set( cc ); }
+
+    public static ConnectionCustomizer getThreadConnectionCustomizer()
+    { return threadLocalConnectionCustomizer.get(); }
+
+    public static void unsetThreadConnectionCustomizer()
+    { setThreadConnectionCustomizer( null ); }
+    */
diff --git a/src/classes/com/mchange/v2/c3p0/impl/C3P0JavaBeanObjectFactory.java b/src/classes/com/mchange/v2/c3p0/impl/C3P0JavaBeanObjectFactory.java
new file mode 100644
index 0000000..968635c
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/C3P0JavaBeanObjectFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.lang.reflect.Constructor;
+import java.util.Map;
+import java.util.Set;
+import com.mchange.v2.c3p0.C3P0Registry;
+import com.mchange.v2.naming.JavaBeanObjectFactory;
+
+public class C3P0JavaBeanObjectFactory extends JavaBeanObjectFactory
+{
+    private final static Class[]  CTOR_ARG_TYPES = new Class[] { boolean.class };
+    private final static Object[] CTOR_ARGS      = new Object[] { Boolean.FALSE };
+
+    protected Object createBlankInstance(Class beanClass) throws Exception
+    {
+	if ( IdentityTokenized.class.isAssignableFrom( beanClass ) )
+	    {
+		Constructor ctor = beanClass.getConstructor( CTOR_ARG_TYPES );
+		return ctor.newInstance( CTOR_ARGS );
+	    }
+	else
+	    return super.createBlankInstance( beanClass );
+    }
+
+    protected Object findBean(Class beanClass, Map propertyMap, Set refProps ) throws Exception
+    {
+	Object out = super.findBean( beanClass, propertyMap, refProps );
+	if (out instanceof IdentityTokenized)
+	    out = C3P0Registry.reregister( (IdentityTokenized) out );
+	//System.err.println("--> findBean()");
+	//System.err.println(out);
+	return out;
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/impl/C3P0PooledConnection.java b/src/classes/com/mchange/v2/c3p0/impl/C3P0PooledConnection.java
new file mode 100644
index 0000000..db10ed0
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/C3P0PooledConnection.java
@@ -0,0 +1,1179 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.lang.reflect.*;
+import java.sql.*;
+import java.util.*;
+import javax.sql.*;
+import com.mchange.v2.log.*;
+import com.mchange.v2.sql.*;
+import com.mchange.v2.sql.filter.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v2.c3p0.stmt.*;
+import com.mchange.v2.c3p0.C3P0ProxyConnection;
+import com.mchange.v2.c3p0.util.ConnectionEventSupport;
+import com.mchange.v2.lang.ObjectUtils;
+
+public final class C3P0PooledConnection extends AbstractC3P0PooledConnection
+{
+    final static MLogger logger = MLog.getLogger( C3P0PooledConnection.class );
+
+    final static Class[]     PROXY_CTOR_ARGS = new Class[]{ InvocationHandler.class };
+
+    final static Constructor CON_PROXY_CTOR;
+
+    final static Method RS_CLOSE_METHOD;
+    final static Method STMT_CLOSE_METHOD;
+
+    final static Object[] CLOSE_ARGS;
+
+    final static Set OBJECT_METHODS;
+
+    /**
+     * @deprecated use or rewrite in terms of ReflectUtils.findProxyConstructor()
+     */
+    private static Constructor createProxyConstructor(Class intfc) throws NoSuchMethodException
+    { 
+	Class[] proxyInterfaces = new Class[] { intfc };
+	Class proxyCl = Proxy.getProxyClass(C3P0PooledConnection.class.getClassLoader(), proxyInterfaces);
+	return proxyCl.getConstructor( PROXY_CTOR_ARGS ); 
+    }
+
+    static
+    {
+	try
+	    {
+		CON_PROXY_CTOR = createProxyConstructor( ProxyConnection.class );
+
+		Class[] argClasses = new Class[0];
+		RS_CLOSE_METHOD = ResultSet.class.getMethod("close", argClasses);
+		STMT_CLOSE_METHOD = Statement.class.getMethod("close", argClasses);
+
+		CLOSE_ARGS = new Object[0];
+
+		OBJECT_METHODS = Collections.unmodifiableSet( new HashSet( Arrays.asList( Object.class.getMethods() ) ) );
+	    }
+	catch (Exception e)
+	    { 
+		//e.printStackTrace();
+		logger.log(MLevel.SEVERE, "An Exception occurred in static initializer of" + C3P0PooledConnection.class.getName(), e);
+		throw new InternalError("Something is very wrong, or this is a pre 1.3 JVM." +
+					"We cannot set up dynamic proxies and/or methods!");
+	    }
+    }
+
+    //MT: post-constructor constants
+    final ConnectionTester connectionTester;
+    final boolean autoCommitOnClose;
+    final boolean forceIgnoreUnresolvedTransactions;
+    final boolean supports_setTypeMap;
+    final boolean supports_setHoldability;
+    final int dflt_txn_isolation;
+    final String dflt_catalog;
+    final int dflt_holdability;
+
+    //MT: thread-safe
+    final ConnectionEventSupport ces = new ConnectionEventSupport(this);
+
+    //MT: threadsafe, but reassigned (on close)
+    volatile Connection physicalConnection;
+    volatile Exception  invalidatingException = null;
+
+    //MT: threadsafe, but reassigned, and a read + reassignment must happen
+    //    atomically. protected by this' lock.
+    ProxyConnection exposedProxy;
+
+    //MT: protected by this' lock
+    int connection_status = ConnectionTester.CONNECTION_IS_OKAY;
+
+    /*
+     * contains all unclosed Statements not managed by a StatementCache
+     * associated with the physical connection
+     *
+     * MT: protected by its own lock, not reassigned
+     */
+    final Set uncachedActiveStatements = Collections.synchronizedSet( new HashSet() );
+
+    //MT: Thread-safe, assigned
+    volatile GooGooStatementCache scache;
+    volatile boolean isolation_lvl_nondefault = false;
+    volatile boolean catalog_nondefault       = false; 
+    volatile boolean holdability_nondefault   = false; 
+
+    public C3P0PooledConnection(Connection con, 
+				ConnectionTester connectionTester,
+				boolean autoCommitOnClose, 
+				boolean forceIgnoreUnresolvedTransactions,
+				ConnectionCustomizer cc,
+				String pdsIdt) throws SQLException
+    { 
+	try
+	    {
+		if (cc != null)
+		    cc.onAcquire( con, pdsIdt );
+	    }
+	catch (Exception e)
+	    { throw SqlUtils.toSQLException(e); }
+
+	this.physicalConnection = con; 
+	this.connectionTester = connectionTester;
+	this.autoCommitOnClose = autoCommitOnClose;
+	this.forceIgnoreUnresolvedTransactions = forceIgnoreUnresolvedTransactions;
+	this.supports_setTypeMap = C3P0ImplUtils.supportsMethod(con, "setTypeMap", new Class[]{ Map.class });
+	this.supports_setHoldability = C3P0ImplUtils.supportsMethod(con, "setHoldability", new Class[]{ int.class });
+	this.dflt_txn_isolation = con.getTransactionIsolation();
+	this.dflt_catalog = con.getCatalog();
+	this.dflt_holdability = (supports_setHoldability ? con.getHoldability() : ResultSet.CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    //used by C3P0PooledConnectionPool
+    Connection getPhysicalConnection()
+    { return physicalConnection; }
+
+    boolean isClosed() throws SQLException
+    { return (physicalConnection == null); }
+
+    void initStatementCache( GooGooStatementCache scache )
+    { this.scache = scache; }
+
+
+    //DEBUG
+    //Exception origGet = null;
+
+    // synchronized to protect exposedProxy
+    public synchronized Connection getConnection()
+	throws SQLException
+    { 
+	if ( exposedProxy != null)
+	    {
+		//DEBUG
+		//System.err.println("[DOUBLE_GET_TESTER] -- double getting a Connection from " + this );
+		//new Exception("[DOUBLE_GET_TESTER] -- Double-Get Stack Trace").printStackTrace();
+		//origGet.printStackTrace();
+
+// 		System.err.println("c3p0 -- Uh oh... getConnection() was called on a PooledConnection when " +
+// 				   "it had already provided a client with a Connection that has not yet been " +
+// 				   "closed. This probably indicates a bug in the connection pool!!!");
+
+		logger.warning("c3p0 -- Uh oh... getConnection() was called on a PooledConnection when " +
+			       "it had already provided a client with a Connection that has not yet been " +
+			       "closed. This probably indicates a bug in the connection pool!!!");
+
+		return exposedProxy;
+	    }
+	else
+	    { return getCreateNewConnection(); }
+    }
+
+    // must be called from sync'ed method to protecte
+    // exposedProxy
+    private Connection getCreateNewConnection()
+	throws SQLException
+    {
+	try
+	    {
+		//DEBUG
+		//origGet = new Exception("[DOUBLE_GET_TESTER] -- Orig Get");
+		
+		ensureOkay();
+		/*
+		 * we reset the physical connection when we close an exposed proxy
+		 * no need to do it again when we create one
+		 */
+		//reset();
+		return (exposedProxy = createProxyConnection()); 
+	    }
+	catch (SQLException e)
+	    { throw e; }
+	catch (Exception e)
+	    {
+		//e.printStackTrace();
+		logger.log(MLevel.WARNING, "Failed to acquire connection!", e);
+		throw new SQLException("Failed to acquire connection!");
+	    }
+    }
+
+    public void closeAll() throws SQLException
+    {
+	if (scache != null)
+	    scache.closeAll( physicalConnection );
+    }
+
+    public void close() throws SQLException
+    { this.close( false ); }
+
+    //TODO: factor out repetitive debugging code
+    private synchronized void close(boolean known_invalid) throws SQLException
+    {
+	//System.err.println("Closing " + this);
+	if ( physicalConnection != null )
+	    {
+		try
+		    { 
+			StringBuffer debugOnlyLog = null;
+			if ( Debug.DEBUG && known_invalid )
+			    {
+				debugOnlyLog = new StringBuffer();
+				debugOnlyLog.append("[ exceptions: ");
+			    }
+
+			Exception exc = cleanupUncachedActiveStatements();
+			if (Debug.DEBUG && exc != null) 
+			    {
+				if (known_invalid)
+				    debugOnlyLog.append( exc.toString() + ' ' );
+				else
+				    logger.log(MLevel.WARNING, "An exception occurred while cleaning up uncached active Statements.", exc);
+				    //exc.printStackTrace();
+			    }
+
+			try 
+			    { 
+				// we've got to use silentClose() rather than close() here,
+				// 'cuz if there's still an exposedProxy (say a user forgot to
+				// close his Connection) before we close, and we use regular (loud)
+				// close, we will try to check this dead or dying PooledConnection
+				// back into the pool. We only want to do this when close is called
+				// on user proxies, and the underlying PooledConnection might still
+				// be good. The PooledConnection itself should only be closed by the
+				// pool.
+				if (exposedProxy != null)
+				    exposedProxy.silentClose( known_invalid );
+			    }
+			catch (Exception e)
+			    {
+				if (Debug.DEBUG)
+				    {
+					if (known_invalid)
+					    debugOnlyLog.append( e.toString() + ' ' );
+					else
+					    logger.log(MLevel.WARNING, "An exception occurred.", exc);
+					    //e.printStackTrace();
+				    }
+				exc = e;
+			    }
+			try
+			    { this.closeAll(); }
+			catch (Exception e)
+			    {
+				if (Debug.DEBUG)
+				    {
+					if (known_invalid)
+					    debugOnlyLog.append( e.toString() + ' ' );
+					else
+					    logger.log(MLevel.WARNING, "An exception occurred.", exc);
+					    //e.printStackTrace();
+				    }
+				exc = e;
+			    }
+			
+			try { physicalConnection.close(); }
+			catch (Exception e)
+			    {
+				if (Debug.DEBUG)
+				    {
+					if (known_invalid)
+					    debugOnlyLog.append( e.toString() + ' ' );
+					else
+					    logger.log(MLevel.WARNING, "An exception occurred.", exc);
+					    e.printStackTrace();
+				    }
+				exc = e;
+			    }
+
+			if (exc != null)
+			    {
+				if (known_invalid)
+				    {
+					debugOnlyLog.append(" ]");
+					if (Debug.DEBUG)
+					    {
+// 						System.err.print("[DEBUG]" + this + ": while closing a PooledConnection known to be invalid, ");
+// 						System.err.println("  some exceptions occurred. This is probably not a problem:");
+// 						System.err.println( debugOnlyLog.toString() );
+
+
+						logger.fine(this + ": while closing a PooledConnection known to be invalid, " +
+							    "  some exceptions occurred. This is probably not a problem: " +
+							    debugOnlyLog.toString() );
+					    }
+				    }
+				else
+				    throw new SQLException("At least one error occurred while attempting " +
+							   "to close() the PooledConnection: " + exc);
+			    }
+			if (Debug.TRACE == Debug.TRACE_MAX)
+			    logger.fine("C3P0PooledConnection closed. [" + this + ']');
+			    //System.err.println("C3P0PooledConnection closed. [" + this + ']');
+		    }
+		finally
+		    { physicalConnection = null; }
+	    }
+    }
+
+    public void addConnectionEventListener(ConnectionEventListener listener)
+    { ces.addConnectionEventListener( listener ); }
+
+    public void removeConnectionEventListener(ConnectionEventListener listener)
+    { ces.removeConnectionEventListener( listener ); }
+
+    private void reset() throws SQLException
+    { reset( false ); }
+
+    private void reset( boolean known_resolved_txn ) throws SQLException
+    {
+	ensureOkay();
+	C3P0ImplUtils.resetTxnState( physicalConnection, forceIgnoreUnresolvedTransactions, autoCommitOnClose, known_resolved_txn );
+	if (isolation_lvl_nondefault)
+	    {
+		physicalConnection.setTransactionIsolation( dflt_txn_isolation );
+		isolation_lvl_nondefault = false; 
+	    }
+	if (catalog_nondefault)
+	    {
+		physicalConnection.setCatalog( dflt_catalog );
+		catalog_nondefault = false; 
+	    }
+	if (holdability_nondefault) //we don't test if holdability is supported, 'cuz it can never go nondefault if it's not.
+	    {
+		physicalConnection.setHoldability( dflt_holdability );
+		holdability_nondefault = false; 
+	    }
+
+	try
+	    { physicalConnection.setReadOnly( false ); }
+	catch ( Throwable t )
+	    {
+		if (logger.isLoggable( MLevel.FINE ))
+		    logger.log(MLevel.FINE, "A Throwable occurred while trying to reset the readOnly property of our Connection to false!", t);
+	    }
+
+	try
+	    { if (supports_setTypeMap) physicalConnection.setTypeMap( Collections.EMPTY_MAP ); }
+	catch ( Throwable t )
+	    {
+		if (logger.isLoggable( MLevel.FINE ))
+		    logger.log(MLevel.FINE, "A Throwable occurred while trying to reset the typeMap property of our Connection to Collections.EMPTY_MAP!", t);
+	    }
+    }
+
+    boolean closeAndRemoveResultSets(Set rsSet)
+    {
+	boolean okay = true;
+	synchronized (rsSet)
+	    {
+		for (Iterator ii = rsSet.iterator(); ii.hasNext(); )
+		    {
+			ResultSet rs = (ResultSet) ii.next();
+			try
+			    { rs.close(); }
+			catch (SQLException e)
+			    {
+				if (Debug.DEBUG)
+				    logger.log(MLevel.WARNING, "An exception occurred while cleaning up a ResultSet.", e);
+				    //e.printStackTrace();
+				okay = false;
+			    }
+			finally 
+			    { ii.remove(); }
+		    }
+	    }
+	return okay;
+    }
+
+    void ensureOkay() throws SQLException
+    {
+	if (physicalConnection == null)
+	    throw new SQLException( invalidatingException == null ?
+				    "Connection is closed or broken." :
+				    "Connection is broken. Invalidating Exception: " + invalidatingException.toString());
+    }
+
+    boolean closeAndRemoveResourcesInSet(Set s, Method closeMethod)
+    {
+	boolean okay = true;
+	
+	Set temp;
+	synchronized (s)
+	    { temp = new HashSet( s ); }
+
+	for (Iterator ii = temp.iterator(); ii.hasNext(); )
+	    {
+		Object rsrc = ii.next();
+		try
+		    { closeMethod.invoke(rsrc, CLOSE_ARGS); }
+		catch (Exception e)
+		    {
+			Throwable t = e;
+			if (t instanceof InvocationTargetException)
+			    t = ((InvocationTargetException) e).getTargetException();
+			logger.log(MLevel.WARNING, "An exception occurred while cleaning up a resource.", t);
+			//t.printStackTrace();
+			okay = false;
+		    }
+		finally 
+		    { s.remove( rsrc ); }
+	    }
+
+	// We had to abandon the idea of simply iterating over s directly, because	
+	// our resource close methods sometimes try to remove the resource from
+	// its parent Set. This is important (when the user closes the resources
+	// directly), but leads to ConcurrenModificationExceptions while we are
+	// iterating over the Set to close. So, now we iterate over a copy, but remove
+	// from the original Set. Since removal is idempotent, it don't matter if
+	// the close method already provoked a remove. Sucks that we have to copy
+	// the set though.
+	//
+	// Original (direct iteration) version:
+	//
+	//  	synchronized (s)
+	//  	    {
+	//  		for (Iterator ii = s.iterator(); ii.hasNext(); )
+	//  		    {
+	//  			Object rsrc = ii.next();
+	//  			try
+	//  			    { closeMethod.invoke(rsrc, CLOSE_ARGS); }
+	//  			catch (Exception e)
+	//  			    {
+	//  				Throwable t = e;
+	//  				if (t instanceof InvocationTargetException)
+	//  				    t = ((InvocationTargetException) e).getTargetException();
+	//  				t.printStackTrace();
+	//  				okay = false;
+	//  			    }
+	//  			finally 
+	//  			    { ii.remove(); }
+	//  		    }
+	//  	    }
+
+
+	return okay;
+    }
+
+    private SQLException cleanupUncachedActiveStatements()
+    {
+	//System.err.println("IN  uncachedActiveStatements.size(): " + uncachedActiveStatements.size());
+
+	boolean okay = closeAndRemoveResourcesInSet(uncachedActiveStatements, STMT_CLOSE_METHOD);
+
+	//System.err.println("OUT uncachedActiveStatements.size(): " + uncachedActiveStatements.size());
+
+	if (okay)
+	    return null;
+	else
+	    return new SQLException("An exception occurred while trying to " +
+				    "clean up orphaned resources.");
+    }
+
+    ProxyConnection createProxyConnection() throws Exception
+    {
+	// we should always have a separate handler for each proxy connection, so
+	// that object methods behave as expected... the handler covers
+	// all object methods on behalf of the proxy.
+	InvocationHandler handler = new ProxyConnectionInvocationHandler();
+	return (ProxyConnection) CON_PROXY_CTOR.newInstance( new Object[] {handler} );
+    }
+
+    Statement createProxyStatement( Statement innerStmt ) throws Exception
+    { return this.createProxyStatement( false, innerStmt ); }
+
+
+    private static class StatementProxyingSetManagedResultSet extends SetManagedResultSet
+    {
+	private Statement proxyStatement;
+
+	StatementProxyingSetManagedResultSet(Set activeResultSets)
+	{ super( activeResultSets ); }
+
+	public void setProxyStatement( Statement proxyStatement )
+	{ this.proxyStatement = proxyStatement; }
+
+	public Statement getStatement() throws SQLException
+	{ return (proxyStatement == null ? super.getStatement() : proxyStatement); }
+    }
+
+    /*
+     * TODO: factor all this convolution out into
+     *       C3P0Statement
+     */
+    Statement createProxyStatement( //final Method cachedStmtProducingMethod, 
+				    //final Object[] cachedStmtProducingMethodArgs, 
+				   final boolean inner_is_cached,
+				   final Statement innerStmt) throws Exception
+    {
+	final Set activeResultSets = Collections.synchronizedSet( new HashSet() );
+	final Connection parentConnection = exposedProxy;
+
+	if (Debug.DEBUG && parentConnection == null)
+	    {
+// 		System.err.print("PROBABLE C3P0 BUG -- ");
+// 		System.err.println(this + ": created a proxy Statement when there is no active, exposed proxy Connection???");
+
+		logger.warning("PROBABLE C3P0 BUG -- " +
+			       this + ": created a proxy Statement when there is no active, exposed proxy Connection???");
+
+	    }
+
+
+	//we can use this one wrapper under all circumstances
+	//except jdbc3 CallableStatement multiple open ResultSets...
+	//avoid object allocation in statement methods where possible.
+	final StatementProxyingSetManagedResultSet mainResultSet = new StatementProxyingSetManagedResultSet( activeResultSets );
+
+	class WrapperStatementHelper
+	{
+	    Statement wrapperStmt;
+	    Statement nakedInner;
+
+	    public WrapperStatementHelper(Statement wrapperStmt, Statement nakedInner)
+	    {
+		this.wrapperStmt = wrapperStmt;
+		this.nakedInner = nakedInner;
+
+		if (! inner_is_cached)
+		    uncachedActiveStatements.add( wrapperStmt );
+	    }
+
+	    private boolean closeAndRemoveActiveResultSets()
+	    { return closeAndRemoveResultSets( activeResultSets ); }
+
+	    public ResultSet wrap(ResultSet rs)
+	    {
+		if (mainResultSet.getInner() == null)
+		    {
+			mainResultSet.setInner(rs);
+			mainResultSet.setProxyStatement( wrapperStmt );
+			return mainResultSet;
+		    }
+		else
+		    {
+			//for the case of multiple open ResultSets
+			StatementProxyingSetManagedResultSet out 
+			    = new StatementProxyingSetManagedResultSet( activeResultSets );
+			out.setInner( rs );
+			out.setProxyStatement( wrapperStmt );
+			return out;
+		    }
+	    }
+
+	    public void doClose()
+		throws SQLException
+	    {
+		boolean okay = closeAndRemoveActiveResultSets();
+
+		if (inner_is_cached) //this statement was cached
+		    scache.checkinStatement( innerStmt );
+		else
+		    {
+			innerStmt.close();
+			uncachedActiveStatements.remove( wrapperStmt ); 
+		    }
+
+		if (!okay)
+		    throw new SQLException("Failed to close an orphaned ResultSet properly.");
+	    }
+
+	    public Object doRawStatementOperation(Method m, Object target, Object[] args) 
+		throws IllegalAccessException, InvocationTargetException, SQLException
+	    {
+		if (target == C3P0ProxyStatement.RAW_STATEMENT)
+		    target = nakedInner;
+		for (int i = 0, len = args.length; i < len; ++i)
+		    if (args[i] == C3P0ProxyStatement.RAW_STATEMENT)
+			args[i] = nakedInner;
+		
+		Object out = m.invoke(target, args);
+		
+		if (out instanceof ResultSet)
+		    out = wrap( (ResultSet) out );
+
+		return out;
+	    }
+	}
+
+	if (innerStmt instanceof CallableStatement)
+	    {
+		class ProxyCallableStatement extends FilterCallableStatement implements C3P0ProxyStatement
+		    {
+			WrapperStatementHelper wsh;
+
+			ProxyCallableStatement(CallableStatement is)
+			{ 
+			    super( is ); 
+			    this.wsh = new WrapperStatementHelper(this, is);
+			}
+
+			public Connection getConnection()
+			{ return parentConnection; }
+
+			public ResultSet getResultSet() throws SQLException
+			{ return wsh.wrap( super.getResultSet() ); }
+			
+			public ResultSet getGeneratedKeys() throws SQLException
+			{ return wsh.wrap( super.getGeneratedKeys() ); }
+			
+			public ResultSet executeQuery(String sql) throws SQLException
+			{ return wsh.wrap( super.executeQuery(sql) ); }
+			
+			public ResultSet executeQuery() throws SQLException
+			{ return wsh.wrap( super.executeQuery() ); }
+			
+			public Object rawStatementOperation(Method m, Object target, Object[] args) 
+			    throws IllegalAccessException, InvocationTargetException, SQLException
+			{ return wsh.doRawStatementOperation( m, target, args); }
+
+			public void close() throws SQLException
+			{ wsh.doClose(); }
+		    }
+
+		return new ProxyCallableStatement((CallableStatement) innerStmt );
+	    }
+	else if (innerStmt instanceof PreparedStatement)
+	    {
+		class ProxyPreparedStatement extends FilterPreparedStatement implements C3P0ProxyStatement
+		    {
+			WrapperStatementHelper wsh;
+
+			ProxyPreparedStatement(PreparedStatement ps)
+			{ 
+			    super( ps ); 
+			    this.wsh = new WrapperStatementHelper(this, ps);
+			}
+
+			public Connection getConnection()
+			{ return parentConnection; }
+
+			public ResultSet getResultSet() throws SQLException
+			{ return wsh.wrap( super.getResultSet() ); }
+			
+			public ResultSet getGeneratedKeys() throws SQLException
+			{ return wsh.wrap( super.getGeneratedKeys() ); }
+			
+			public ResultSet executeQuery(String sql) throws SQLException
+			{ return wsh.wrap( super.executeQuery(sql) ); }
+			
+			public ResultSet executeQuery() throws SQLException
+			{ return wsh.wrap( super.executeQuery() ); }
+			
+			public Object rawStatementOperation(Method m, Object target, Object[] args) 
+			    throws IllegalAccessException, InvocationTargetException, SQLException
+			{ return wsh.doRawStatementOperation( m, target, args); }
+
+			public void close() throws SQLException
+			{ wsh.doClose(); }
+		    }
+
+		return new ProxyPreparedStatement((PreparedStatement) innerStmt );
+	    }
+	else
+	    {
+		class ProxyStatement extends FilterStatement implements C3P0ProxyStatement
+		    {
+			WrapperStatementHelper wsh;
+
+			ProxyStatement(Statement s)
+			{ 
+			    super( s ); 
+			    this.wsh = new WrapperStatementHelper(this, s);
+			}
+
+			public Connection getConnection()
+			{ return parentConnection; }
+
+			public ResultSet getResultSet() throws SQLException
+			{ return wsh.wrap( super.getResultSet() ); }
+			
+			public ResultSet getGeneratedKeys() throws SQLException
+			{ return wsh.wrap( super.getGeneratedKeys() ); }
+			
+			public ResultSet executeQuery(String sql) throws SQLException
+			{ return wsh.wrap( super.executeQuery(sql) ); }
+			
+			public Object rawStatementOperation(Method m, Object target, Object[] args) 
+			    throws IllegalAccessException, InvocationTargetException, SQLException
+			{ return wsh.doRawStatementOperation( m, target, args); }
+
+			public void close() throws SQLException
+			{ wsh.doClose(); }
+		    }
+
+		return new ProxyStatement( innerStmt );
+	    }
+    }
+
+    final class ProxyConnectionInvocationHandler implements InvocationHandler
+    {
+	//MT: ThreadSafe, but reassigned -- protected by this' lock
+	Connection activeConnection = physicalConnection;
+	DatabaseMetaData metaData   = null;
+	boolean connection_error_signaled = false;
+		
+	/*
+	 * contains all unclosed ResultSets derived from this Connection's metadata
+	 * associated with the physical connection
+	 *
+	 * MT: protected by this' lock
+	 */
+	final Set activeMetaDataResultSets = new HashSet();
+		
+	//being careful with doRawConnectionOperation
+	//we initialize lazily, because this will be very rarely used
+	Set doRawResultSets = null;
+
+	boolean txn_known_resolved = true;
+	
+	public String toString()
+	{ return "C3P0ProxyConnection [Invocation Handler: " + super.toString() + ']'; }
+		
+	private Object doRawConnectionOperation(Method m, Object target, Object[] args) 
+	    throws IllegalAccessException, InvocationTargetException, SQLException, Exception
+	{
+	    if (activeConnection == null)
+		throw new SQLException("Connection previously closed. You cannot operate on a closed Connection.");
+			    
+	    if (target == C3P0ProxyConnection.RAW_CONNECTION)
+		target = activeConnection;
+	    for (int i = 0, len = args.length; i < len; ++i)
+		if (args[i] == C3P0ProxyConnection.RAW_CONNECTION)
+		    args[i] = activeConnection;
+		    
+	    Object out = m.invoke(target, args);
+		    
+	    // we never cache Statements generated by an operation on the raw Connection
+	    if (out instanceof Statement)
+		out = createProxyStatement( false, (Statement) out );
+	    else if (out instanceof ResultSet)
+		{
+		    if (doRawResultSets == null)
+			doRawResultSets = new HashSet();
+		    out = new NullStatementSetManagedResultSet( (ResultSet) out, doRawResultSets );	
+		}
+	    return out;
+	}
+		
+	public synchronized Object invoke(Object proxy, Method m, Object[] args)
+	    throws Throwable
+	{
+	    if ( OBJECT_METHODS.contains( m ) )
+		return m.invoke( this, args );
+	    
+	    try
+		{
+		    String mname = m.getName();
+		    if (activeConnection != null)
+			{	
+			    if (mname.equals("rawConnectionOperation"))
+				{
+				    ensureOkay();
+				    txn_known_resolved = false;
+
+				    return doRawConnectionOperation((Method) args[0], args[1], (Object[]) args[2]);    
+				}
+			    else if (mname.equals("setTransactionIsolation"))
+				{
+				    ensureOkay();
+
+				    //don't modify txn_known_resolved
+
+				    m.invoke( activeConnection, args );
+
+				    int lvl = ((Integer) args[0]).intValue();
+				    isolation_lvl_nondefault = (lvl != dflt_txn_isolation);
+
+				    //System.err.println("updated txn isolation to " + lvl + ", nondefault level? " + isolation_lvl_nondefault);
+
+				    return null;
+				}
+			    else if (mname.equals("setCatalog"))
+				{
+				    ensureOkay();
+
+				    //don't modify txn_known_resolved
+
+				    m.invoke( activeConnection, args );
+
+				    String catalog = (String) args[0];
+				    catalog_nondefault = ObjectUtils.eqOrBothNull(catalog, dflt_catalog);
+
+				    return null;
+				}
+			    else if (mname.equals("setHoldability"))
+				{
+				    ensureOkay();
+
+				    //don't modify txn_known_resolved
+
+				    m.invoke( activeConnection, args ); //will throw an exception if setHoldability() not supported...
+
+				    int holdability = ((Integer) args[0]).intValue();
+				    holdability_nondefault = (holdability != dflt_holdability);
+
+				    return null;
+				}
+			    else if (mname.equals("createStatement"))
+				{
+				    ensureOkay();
+				    txn_known_resolved = false;
+	
+				    Object stmt = m.invoke( activeConnection, args );
+				    return createProxyStatement( (Statement) stmt );
+				}
+			    else if (mname.equals("prepareStatement"))
+				{
+				    ensureOkay();
+				    txn_known_resolved = false;
+	
+				    Object pstmt;
+				    if (scache == null)
+					{
+					    pstmt = m.invoke( activeConnection, args );
+					    return createProxyStatement( (Statement) pstmt );
+					}
+				    else
+					{
+					    pstmt = scache.checkoutStatement( physicalConnection,
+									      m, 
+									      args );
+					    return createProxyStatement( true,
+									 (Statement) pstmt );
+					}
+				}
+			    else if (mname.equals("prepareCall"))
+				{
+				    ensureOkay();
+				    txn_known_resolved = false;
+	
+				    Object cstmt;
+				    if (scache == null)
+					{
+					    cstmt = m.invoke( activeConnection, args );
+					    return createProxyStatement( (Statement) cstmt ); 
+					}
+				    else
+					{
+					    cstmt = scache.checkoutStatement( physicalConnection, m, args );
+					    return createProxyStatement( true,
+									 (Statement) cstmt );
+					}
+				}
+			    else if (mname.equals("getMetaData"))
+				{
+				    ensureOkay();
+				    txn_known_resolved = false; //views of tables etc. might be txn dependent
+	
+				    DatabaseMetaData innerMd = activeConnection.getMetaData();
+				    if (metaData == null)
+					{
+					    // exposedProxy is protected by C3P0PooledConnection.this' lock
+					    synchronized (C3P0PooledConnection.this)
+						{ metaData = new SetManagedDatabaseMetaData(innerMd, activeMetaDataResultSets, exposedProxy); }
+					}
+				    return metaData;
+				}
+			    else if (mname.equals("silentClose"))
+				{
+				    //the PooledConnection doesn't have to be okay
+	
+				    doSilentClose( proxy, ((Boolean) args[0]).booleanValue(), this.txn_known_resolved );
+				    return null;
+				}
+			    else if ( mname.equals("close") )
+				{
+				    //the PooledConnection doesn't have to be okay
+	
+				    Exception e = doSilentClose( proxy, false, this.txn_known_resolved );
+				    if (! connection_error_signaled)
+					ces.fireConnectionClosed();
+				    //System.err.println("close() called on a ProxyConnection.");
+				    if (e != null)
+					{
+					    // 					    System.err.print("user close exception -- ");
+					    // 					    e.printStackTrace();
+					    throw e;
+					}
+				    else
+					return null;
+				}
+// 			    else if ( mname.equals("finalize") ) //REMOVE THIS CASE -- TMP DEBUG
+// 				{
+// 				    System.err.println("Connection apparently finalized!");
+// 				    return m.invoke( activeConnection, args );
+// 				}
+			    else
+				{
+				    ensureOkay();
+					    
+
+				    // we've disabled setting txn_known_resolved to true, ever, because
+				    // we failed to deal with the case that clients would work with previously
+				    // acquired Statements and ResultSets after a commit(), rollback(), or setAutoCommit().
+				    // the new non-reflective proxies have been modified to deal with this case.
+				    // here, with soon-to-be-deprecated in "traditional reflective proxies mode"
+				    // we are reverting to the conservative, always-presume-you-have-to-rollback
+				    // policy.
+
+				    //txn_known_resolved = ( mname.equals("commit") || mname.equals( "rollback" ) || mname.equals( "setAutoCommit" ) );
+				    txn_known_resolved = false; 
+
+				    return m.invoke( activeConnection, args );
+				}
+			}
+		    else
+			{
+			    if (mname.equals("close") || 
+				mname.equals("silentClose"))
+				return null;
+			    else if (mname.equals("isClosed"))
+				return Boolean.TRUE;
+			    else
+				{
+				    throw new SQLException("You can't operate on " +
+							   "a closed connection!!!");
+				}
+			}
+		}
+	    catch (InvocationTargetException e)
+		{
+		    Throwable convertMe = e.getTargetException();
+		    SQLException sqle = handleMaybeFatalToPooledConnection( convertMe, proxy, false );
+		    sqle.fillInStackTrace();
+		    throw sqle;
+		}
+	}
+	
+	private Exception doSilentClose(Object proxyConnection, boolean pooled_connection_is_dead)
+	{ return doSilentClose( proxyConnection, pooled_connection_is_dead, false ); }
+
+	private Exception doSilentClose(Object proxyConnection, boolean pooled_connection_is_dead, boolean known_resolved_txn)
+	{
+	    if ( activeConnection != null )
+		{
+		    synchronized ( C3P0PooledConnection.this ) //uh oh... this is a nested lock acq... is there a deadlock hazard here?
+			{
+			    if ( C3P0PooledConnection.this.exposedProxy == proxyConnection )
+				{
+				    C3P0PooledConnection.this.exposedProxy = null;
+				    //System.err.println("Reset exposed proxy.");
+					    
+				    //DEBUG
+				    //origGet = null;
+				}
+			    else //else case -- DEBUG only
+				logger.warning("(c3p0 issue) doSilentClose( ... ) called on a proxyConnection " +
+					       "other than the current exposed proxy for its PooledConnection. [exposedProxy: " +
+					       exposedProxy + ", proxyConnection: " + proxyConnection);
+// 				System.err.println("[DEBUG] WARNING: doSilentClose( ... ) called on a proxyConnection " +
+// 						   "other than the current exposed proxy for its PooledConnection. [exposedProxy: " +
+// 						   exposedProxy + ", proxyConnection: " + proxyConnection);
+			}
+			    
+		    Exception out = null;
+			    
+		    Exception exc1 = null, exc2 = null, exc3 = null, exc4 = null;
+		    try 
+			{ 
+			    if (! pooled_connection_is_dead)
+				C3P0PooledConnection.this.reset(known_resolved_txn); 
+			}
+		    catch (Exception e)
+			{ 
+			    exc1 = e;
+			    // 		    if (Debug.DEBUG)
+			    // 			{
+			    // 			    System.err.print("exc1 -- ");
+			    // 			    exc1.printStackTrace();
+			    // 			}
+			}
+			    
+		    exc2 = cleanupUncachedActiveStatements();
+		    // 	    if (Debug.DEBUG && exc2 != null)
+		    // 		{
+		    // 		    System.err.print("exc2 -- ");
+		    // 		    exc2.printStackTrace();
+		    // 		}
+		    String errSource;
+		    if (doRawResultSets != null)
+			{
+			    activeMetaDataResultSets.addAll( doRawResultSets );
+			    errSource = "DataBaseMetaData or raw Connection operation";
+			}
+		    else
+			errSource = "DataBaseMetaData";
+
+		    if (!closeAndRemoveResultSets( activeMetaDataResultSets ))
+			exc3 = new SQLException("Failed to close some " + errSource + " Result Sets.");
+		    // 	    if (Debug.DEBUG && exc3 != null)
+		    // 		{
+		    // 		    System.err.print("exc3 -- ");
+		    // 		    exc3.printStackTrace();
+		    // 		}
+		    if (scache != null)
+			{
+			    try
+				{ scache.checkinAll( physicalConnection ); }
+			    catch ( Exception e )
+				{ exc4 = e; }
+			    // 		    if (Debug.DEBUG && exc4 != null)
+			    // 			{
+			    // 			    System.err.print("exc4 -- ");
+			    // 			    exc4.printStackTrace();
+			    // 			}
+			}
+			    
+		    if (exc1 != null)
+			{
+			    handleMaybeFatalToPooledConnection( exc1, proxyConnection, true );
+			    out = exc1;
+			}
+		    else if (exc2 != null)
+			{
+			    handleMaybeFatalToPooledConnection( exc2, proxyConnection, true );
+			    out = exc2;
+			}
+		    else if (exc3 != null)
+			{
+			    handleMaybeFatalToPooledConnection( exc3, proxyConnection, true );
+			    out = exc3;
+			}
+		    else if (exc4 != null)
+			{
+			    handleMaybeFatalToPooledConnection( exc4, proxyConnection, true );
+			    out = exc4;
+			}
+			    
+		    // 	    if (out != null)
+		    // 		{
+		    // 		    System.err.print("out -- ");
+		    // 		    out.printStackTrace();
+		    // 		}
+	
+		    activeConnection = null;
+		    return out;
+		}
+	    else
+		return null;
+	}
+	
+	private SQLException handleMaybeFatalToPooledConnection( Throwable t, Object proxyConnection, boolean already_closed )
+	{
+	    //System.err.println("handleMaybeFatalToPooledConnection()");
+	
+	    SQLException sqle = SqlUtils.toSQLException( t );
+	    int status = connectionTester.statusOnException( physicalConnection, sqle );
+	    updateConnectionStatus( status ); 
+	    if (status != ConnectionTester.CONNECTION_IS_OKAY)
+		{
+		    if (Debug.DEBUG)
+			{
+// 			    System.err.print(C3P0PooledConnection.this + " will no longer be pooled because it has been " +
+// 					     "marked invalid by the following Exception: ");
+// 			    t.printStackTrace();
+
+			    logger.log(MLevel.INFO, 
+				       C3P0PooledConnection.this + " will no longer be pooled because it has been marked invalid by an Exception.",
+				       t );
+			}
+			    
+		    invalidatingException = sqle;
+
+		    /*
+		      ------
+		      A users have complained that SQLExceptions ought not close their Connections underneath
+		      them under any circumstance. Signalling the Connection error after updating the Connection
+		      status should be sufficient from the pool's perspective, because the PooledConnection
+		      will be marked broken by the pool and will be destroyed on checkin. I think actually
+		      close()ing the Connection when it appears to be broken rather than waiting for users
+		      to close() it themselves is overly aggressive, so I'm commenting the old behavior out.
+		      The only potential downside to this approach is that users who do not close() in a finally
+		      clause properly might see their close()es skipped by exceptions that previously would
+		      have led to automatic close(). But relying on the automatic close() was never reliable
+		      (since it only ever happened when c3p0 determined a Connection to be absolutely broken),
+		      and is generally speaking a client error that c3p0 ought not be responsible for dealing
+		      with. I think it's right to leave this out. -- swaldman 2004-12-09
+		      ------
+		      
+		      if (! already_closed )
+		          doSilentClose( proxyConnection, true );
+		    */
+
+		    if (! connection_error_signaled)
+			{
+			    ces.fireConnectionErrorOccurred( sqle );
+			    connection_error_signaled = true;
+			}
+		}
+	    return sqle;
+	}
+	
+
+    }
+
+    interface ProxyConnection extends C3P0ProxyConnection
+    { void silentClose( boolean known_invalid ) throws SQLException; }
+
+    public synchronized int getConnectionStatus()
+    { return this.connection_status; }
+
+    private synchronized void updateConnectionStatus(int status)
+    {
+	switch ( this.connection_status )
+	    {
+	    case ConnectionTester.DATABASE_IS_INVALID:
+		//can't get worse than this, do nothing.
+		break;
+	    case ConnectionTester.CONNECTION_IS_INVALID:
+		if (status == ConnectionTester.DATABASE_IS_INVALID)
+		    doBadUpdate(status);
+		break;
+	    case ConnectionTester.CONNECTION_IS_OKAY:
+		if (status != ConnectionTester.CONNECTION_IS_OKAY)
+		    doBadUpdate(status);
+		break;
+	    default:
+		throw new InternalError(this + " -- Illegal Connection Status: " + this.connection_status);
+	    }
+    }
+
+    //must be called from sync'ed method
+    private void doBadUpdate(int new_status)
+    {
+	this.connection_status = new_status;
+	try { this.close( true ); }
+	catch (SQLException e)
+	    {
+// 		System.err.print("Broken Connection Close Error: ");
+// 		e.printStackTrace(); 
+
+		logger.log(MLevel.WARNING, "Broken Connection Close Error. ", e);
+	    }
+    }
+}
+
+
+
+
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/impl/C3P0PooledConnectionPool.java b/src/classes/com/mchange/v2/c3p0/impl/C3P0PooledConnectionPool.java
new file mode 100644
index 0000000..407f346
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/C3P0PooledConnectionPool.java
@@ -0,0 +1,921 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import com.mchange.v2.c3p0.stmt.*;
+import com.mchange.v2.c3p0.ConnectionCustomizer;
+import com.mchange.v2.c3p0.SQLWarnings;
+import com.mchange.v2.c3p0.UnifiedConnectionTester;
+import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.util.LinkedList;
+
+import javax.sql.ConnectionEvent;
+import javax.sql.ConnectionEventListener;
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.PooledConnection;
+
+import com.mchange.v1.db.sql.ConnectionUtils;
+import com.mchange.v2.async.AsynchronousRunner;
+import com.mchange.v2.async.ThreadPoolAsynchronousRunner;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import com.mchange.v2.c3p0.ConnectionTester;
+import com.mchange.v2.c3p0.QueryConnectionTester;
+import com.mchange.v2.resourcepool.CannotAcquireResourceException;
+import com.mchange.v2.resourcepool.ResourcePool;
+import com.mchange.v2.resourcepool.ResourcePoolException;
+import com.mchange.v2.resourcepool.ResourcePoolFactory;
+import com.mchange.v2.resourcepool.TimeoutException;
+import com.mchange.v2.sql.SqlUtils;
+
+public final class C3P0PooledConnectionPool
+{
+    private final static boolean ASYNCHRONOUS_CONNECTION_EVENT_LISTENER = false;
+
+    private final static Throwable[] EMPTY_THROWABLE_HOLDER = new Throwable[1];
+
+    final static MLogger logger = MLog.getLogger( C3P0PooledConnectionPool.class );
+
+    final ResourcePool rp;
+    final ConnectionEventListener cl = new ConnectionEventListenerImpl();
+
+    final ConnectionTester     connectionTester;
+    final GooGooStatementCache scache;
+
+    final int checkoutTimeout;
+
+    final AsynchronousRunner sharedTaskRunner;
+
+    final ThrowableHolderPool thp = new ThrowableHolderPool();
+
+    C3P0PooledConnectionPool( final ConnectionPoolDataSource cpds,
+                    final DbAuth auth,
+                    int min, 
+                    int max, 
+                    int start,
+                    int inc,
+                    int acq_retry_attempts,
+                    int acq_retry_delay,
+                    boolean break_after_acq_failure,
+                    int checkoutTimeout, //milliseconds
+                    int idleConnectionTestPeriod, //seconds
+                    int maxIdleTime, //seconds
+                    int maxIdleTimeExcessConnections, //seconds
+                    int maxConnectionAge, //seconds
+                    int propertyCycle, //seconds
+                    int unreturnedConnectionTimeout, //seconds
+                    boolean debugUnreturnedConnectionStackTraces,
+                    final boolean testConnectionOnCheckout,
+                    final boolean testConnectionOnCheckin,
+                    int maxStatements,
+                    int maxStatementsPerConnection,
+                    final ConnectionTester connectionTester,
+                    final ConnectionCustomizer connectionCustomizer,
+                    final String testQuery,
+                    final ResourcePoolFactory fact,
+                    ThreadPoolAsynchronousRunner taskRunner,
+                    final String parentDataSourceIdentityToken) throws SQLException
+                    {
+        try
+        {
+            if (maxStatements > 0 && maxStatementsPerConnection > 0)
+                this.scache = new DoubleMaxStatementCache( taskRunner, maxStatements, maxStatementsPerConnection );
+            else if (maxStatementsPerConnection > 0)
+                this.scache = new PerConnectionMaxOnlyStatementCache( taskRunner, maxStatementsPerConnection );
+            else if (maxStatements > 0)
+                this.scache = new GlobalMaxOnlyStatementCache( taskRunner, maxStatements );
+            else
+                this.scache = null;
+
+            this.connectionTester = connectionTester;
+
+            this.checkoutTimeout = checkoutTimeout;
+
+            this.sharedTaskRunner = taskRunner;
+
+            class PooledConnectionResourcePoolManager implements ResourcePool.Manager
+            {	
+                //SynchronizedIntHolder totalOpenedCounter  = new SynchronizedIntHolder();
+                //SynchronizedIntHolder connectionCounter   = new SynchronizedIntHolder();
+                //SynchronizedIntHolder failedCloseCounter  = new SynchronizedIntHolder();
+
+                final boolean connectionTesterIsDefault = (connectionTester instanceof DefaultConnectionTester);
+                final boolean c3p0PooledConnections = (cpds instanceof WrapperConnectionPoolDataSource);
+
+                public Object acquireResource() throws Exception
+                { 
+                    PooledConnection out;
+
+                    if ( connectionCustomizer == null)
+                    {
+                        out = (auth.equals( C3P0ImplUtils.NULL_AUTH ) ?
+                               cpds.getPooledConnection() :
+                               cpds.getPooledConnection( auth.getUser(), 
+                                                         auth.getPassword() ) );
+                    }
+                    else
+                    {
+                        try
+                        { 
+                            WrapperConnectionPoolDataSourceBase wcpds = (WrapperConnectionPoolDataSourceBase) cpds;
+
+                            out = (auth.equals( C3P0ImplUtils.NULL_AUTH ) ?
+                                   wcpds.getPooledConnection( connectionCustomizer, parentDataSourceIdentityToken ) :
+                                   wcpds.getPooledConnection( auth.getUser(), 
+                                                              auth.getPassword(),
+                                                              connectionCustomizer, parentDataSourceIdentityToken ) );
+                        }
+                        catch (ClassCastException e)
+                        {
+                            throw SqlUtils.toSQLException("Cannot use a ConnectionCustomizer with a non-c3p0 ConnectionPoolDataSource." +
+                                            " ConnectionPoolDataSource: " + cpds.getClass().getName(), e);
+                        }
+                    }
+
+                    //connectionCounter.increment(); 
+                    //totalOpenedCounter.increment();
+
+                    try
+                    {
+                        if (scache != null)
+                        {
+                            if (c3p0PooledConnections)
+                                ((AbstractC3P0PooledConnection) out).initStatementCache(scache);
+                            else
+                            {
+                                // System.err.print("Warning! StatementPooling not ");
+                                // System.err.print("implemented for external (non-c3p0) ");
+                                // System.err.println("ConnectionPoolDataSources.");
+
+                                logger.warning("StatementPooling not " +
+                                                "implemented for external (non-c3p0) " +
+                                "ConnectionPoolDataSources.");
+                            }
+                        }
+                        
+                        // log and clear any SQLWarnings present upon acquisition
+                        Connection con = null;
+                        try
+                        {
+                            con = out.getConnection();
+                            SQLWarnings.logAndClearWarnings( con );
+                        }
+                        finally
+                        {
+                            //invalidate the proxy Connection
+                            ConnectionUtils.attemptClose( con );
+                        }
+                        
+                        out.addConnectionEventListener( cl );
+                        return out;
+                    }
+                    catch (Exception e)
+                    {
+                        if (logger.isLoggable( MLevel.WARNING ))
+                            logger.warning("A PooledConnection was acquired, but an Exception occurred while preparing it for use. " +
+                            "Attempting to destroy.");
+                        try { destroyResource( out ); }
+                        catch (Exception e2)
+                        {
+                            if (logger.isLoggable( MLevel.WARNING ))
+                                logger.log( MLevel.WARNING, 
+                                                "An Exception occurred while trying to close partially acquired PooledConnection.",
+                                                e2 );
+                        }
+
+                        throw e;
+                    }
+                    finally
+                    {
+                        if (logger.isLoggable( MLevel.FINEST ))
+                            logger.finest(this + ".acquireResource() returning. " );
+                        //"Currently open Connections: " + connectionCounter.getValue() +
+                        //"; Failed close count: " + failedCloseCounter.getValue() +
+                        //"; Total processed by this pool: " + totalOpenedCounter.getValue());
+                    }
+                }
+
+                // REFURBISHMENT:
+                // the PooledConnection refurbishes itself when 
+                // its Connection view is closed, prior to being
+                // checked back in to the pool. But we still may want to
+                // test to make sure it is still good.
+
+                public void refurbishResourceOnCheckout( Object resc ) throws Exception
+                {
+                    if ( testConnectionOnCheckout )
+                    {
+                        if ( logger.isLoggable( MLevel.FINER ) )
+                            finerLoggingTestPooledConnection( resc, "CHECKOUT" );
+                        else
+                            testPooledConnection( resc );
+                    }
+                    if ( connectionCustomizer != null )
+                    {
+                        Connection physicalConnection = null;
+                        try
+                        { 
+                            physicalConnection =  ((AbstractC3P0PooledConnection) resc).getPhysicalConnection();
+                            connectionCustomizer.onCheckOut( physicalConnection, parentDataSourceIdentityToken );
+                        }
+                        catch (ClassCastException e)
+                        {
+                            throw SqlUtils.toSQLException("Cannot use a ConnectionCustomizer with a non-c3p0 PooledConnection." +
+                                            " PooledConnection: " + resc + 
+                                            "; ConnectionPoolDataSource: " + cpds.getClass().getName(), e);
+                        }
+                    }
+                }
+
+                public void refurbishResourceOnCheckin( Object resc ) throws Exception
+                {
+                    if ( connectionCustomizer != null )
+                    {
+                        Connection physicalConnection = null;
+                        try
+                        { 
+                            physicalConnection =  ((AbstractC3P0PooledConnection) resc).getPhysicalConnection();
+                            connectionCustomizer.onCheckIn( physicalConnection, parentDataSourceIdentityToken );
+                            SQLWarnings.logAndClearWarnings( physicalConnection );
+                        }
+                        catch (ClassCastException e)
+                        {
+                            throw SqlUtils.toSQLException("Cannot use a ConnectionCustomizer with a non-c3p0 PooledConnection." +
+                                            " PooledConnection: " + resc + 
+                                            "; ConnectionPoolDataSource: " + cpds.getClass().getName(), e);
+                        }
+                    }
+                    else
+                    {
+                        PooledConnection pc = (PooledConnection) resc;
+                        Connection con = null;
+
+                        try
+                        {
+                            //we don't want any callbacks while we're clearing warnings
+                            pc.removeConnectionEventListener( cl );
+
+                            con = pc.getConnection();
+                            SQLWarnings.logAndClearWarnings(con);
+                        }
+                        finally
+                        {
+                            // close the proxy Connection
+                            ConnectionUtils.attemptClose(con);
+                            
+                            pc.addConnectionEventListener( cl );
+                        }
+                    }
+                    
+                    if ( testConnectionOnCheckin )
+                    { 
+                        if ( logger.isLoggable( MLevel.FINER ) )
+                            finerLoggingTestPooledConnection( resc, "CHECKIN" );
+                        else
+                            testPooledConnection( resc );
+                    }
+                }
+
+                public void refurbishIdleResource( Object resc ) throws Exception
+                { 
+                    if ( logger.isLoggable( MLevel.FINER ) )
+                        finerLoggingTestPooledConnection( resc, "IDLE CHECK" );
+                    else
+                        testPooledConnection( resc );
+                }
+
+                private void finerLoggingTestPooledConnection(Object resc, String testImpetus) throws Exception
+                {
+                    logger.finer("Testing PooledConnection [" + resc + "] on " + testImpetus + ".");
+                    try
+                    {
+                        testPooledConnection( resc );
+                        logger.finer("Test of PooledConnection [" + resc + "] on "+testImpetus+" has SUCCEEDED.");
+                    }
+                    catch (Exception e)
+                    {
+                        logger.log(MLevel.FINER, "Test of PooledConnection [" + resc + "] on "+testImpetus+" has FAILED.", e);
+                        e.fillInStackTrace();
+                        throw e;
+                    }
+                }
+
+                private void testPooledConnection(Object resc) throws Exception
+                { 
+                    PooledConnection pc = (PooledConnection) resc;
+
+                    Throwable[] throwableHolder = EMPTY_THROWABLE_HOLDER;
+                    int status;
+                    Connection conn = null;
+                    Throwable rootCause = null;
+                    try	
+                    { 
+                        //we don't want any callbacks while we're testing the resource
+                        pc.removeConnectionEventListener( cl );
+
+                        conn = pc.getConnection(); //checkout proxy connection
+
+                        // if this is a c3p0 pooled-connection, let's get underneath the
+                        // proxy wrapper, and test the physical connection sometimes. 
+                        // this is faster, when the testQuery would not otherwise be cached,
+                        // and it avoids a potential statusOnException() double-check by the
+                        // PooledConnection implementation should the test query provoke an
+                        // Exception
+                        Connection testConn;
+                        if (scache != null) //when there is a statement cache...
+                        {
+                            // if it's the slow, default query, faster to test the raw Connection 
+                            if (testQuery == null && connectionTesterIsDefault && c3p0PooledConnections)
+                                testConn = ((AbstractC3P0PooledConnection) pc).getPhysicalConnection();
+                            else //test will likely be faster on the proxied Connection, because the test query is probably cached
+                                testConn = conn; 
+                        }
+                        else //where there's no statement cache, better to use the physical connection, if we can get it
+                        {
+                            if (c3p0PooledConnections)
+                                testConn = ((AbstractC3P0PooledConnection) pc).getPhysicalConnection();
+                            else    
+                                testConn = conn;
+                        }
+
+                        if ( testQuery == null )
+                            status = connectionTester.activeCheckConnection( testConn );
+                        else
+                        {
+                            if (connectionTester instanceof UnifiedConnectionTester)
+                            {
+                                throwableHolder = thp.getThrowableHolder();
+                                status = ((UnifiedConnectionTester) connectionTester).activeCheckConnection( testConn, testQuery, throwableHolder );
+                            }
+                            else if (connectionTester instanceof QueryConnectionTester)
+                                status = ((QueryConnectionTester) connectionTester).activeCheckConnection( testConn, testQuery );
+                            else
+                            {
+                                // System.err.println("[c3p0] WARNING: testQuery '" + testQuery +
+                                // "' ignored. Please set a ConnectionTester that implements " +
+                                // "com.mchange.v2.c3p0.advanced.QueryConnectionTester, or use the " +
+                                // "DefaultConnectionTester, to test with the testQuery.");
+
+                                logger.warning("[c3p0] testQuery '" + testQuery +
+                                                "' ignored. Please set a ConnectionTester that implements " +
+                                                "com.mchange.v2.c3p0.QueryConnectionTester, or use the " +
+                                "DefaultConnectionTester, to test with the testQuery.");
+                                status = connectionTester.activeCheckConnection( testConn );
+                            }
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        if (Debug.DEBUG)
+                            logger.log(MLevel.FINE, "A Connection test failed with an Exception.", e);
+                        //e.printStackTrace();
+                        status = ConnectionTester.CONNECTION_IS_INVALID;
+//                      System.err.println("rootCause ------>");
+//                      e.printStackTrace();
+                        rootCause = e;
+                    }
+                    finally
+                    { 
+                        if (rootCause == null)
+                            rootCause = throwableHolder[0];
+                        else if (throwableHolder[0] != null && logger.isLoggable(MLevel.FINE))
+                            logger.log(MLevel.FINE, "Internal Connection Test Exception", throwableHolder[0]);
+                        
+                        if (throwableHolder != EMPTY_THROWABLE_HOLDER)
+                            thp.returnThrowableHolder( throwableHolder );
+                        
+                        ConnectionUtils.attemptClose( conn ); //invalidate proxy connection
+                        pc.addConnectionEventListener( cl );  //should we move this to CONNECTION_IS_OKAY case? (it should work either way)
+                    }
+
+                    switch (status)
+                    {
+                    case ConnectionTester.CONNECTION_IS_OKAY:
+                        break; //no problem, babe
+                    case ConnectionTester.DATABASE_IS_INVALID:
+                        rp.resetPool();
+                        //intentional cascade...
+                    case ConnectionTester.CONNECTION_IS_INVALID:
+                        Exception throwMe;
+                        if (rootCause == null)
+                            throwMe = new SQLException("Connection is invalid");
+                        else
+                            throwMe = SqlUtils.toSQLException("Connection is invalid", rootCause);
+                        throw throwMe;
+                    default:
+                        throw new Error("Bad Connection Tester (" + 
+                                        connectionTester + ") " +
+                                        "returned invalid status (" + status + ").");
+                    }
+                }
+
+                public void destroyResource(Object resc) throws Exception
+                { 
+                    try
+                    {
+                        if ( connectionCustomizer != null )
+                        {
+                            Connection physicalConnection = null;
+                            try
+                            { 
+                                physicalConnection =  ((AbstractC3P0PooledConnection) resc).getPhysicalConnection();
+                                connectionCustomizer.onDestroy( physicalConnection, parentDataSourceIdentityToken );
+                            }
+                            catch (ClassCastException e)
+                            {
+                                throw SqlUtils.toSQLException("Cannot use a ConnectionCustomizer with a non-c3p0 PooledConnection." +
+                                                " PooledConnection: " + resc + 
+                                                "; ConnectionPoolDataSource: " + cpds.getClass().getName(), e);
+                            }
+                            catch (Exception e)
+                            {
+                                if (logger.isLoggable( MLevel.WARNING ))
+                                    logger.log( MLevel.WARNING,
+                                                    "An exception occurred while executing the onDestroy() method of " + connectionCustomizer +
+                                                    ". c3p0 will attempt to destroy the target Connection regardless, but this issue " +
+                                                    " should be investigated and fixed.",
+                                                    e );
+                            }
+                        }
+
+                        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ))
+                            logger.log( MLevel.FINER, "Preparing to destroy PooledConnection: " + resc);
+
+                        ((PooledConnection) resc).close();
+
+                        // inaccurate, as Connections can be removed more than once
+                        //connectionCounter.decrement();
+
+                        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ))
+                            logger.log( MLevel.FINER, 
+                                            "Successfully destroyed PooledConnection: " + resc );
+                        //". Currently open Connections: " + connectionCounter.getValue() +
+                        //"; Failed close count: " + failedCloseCounter.getValue() +
+                        //"; Total processed by this pool: " + totalOpenedCounter.getValue());
+                    }
+                    catch (Exception e)
+                    {
+                        //failedCloseCounter.increment();
+
+                        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ))
+                            logger.log( MLevel.FINER, "Failed to destroy PooledConnection: " + resc );
+                        //". Currently open Connections: " + connectionCounter.getValue() +
+                        //"; Failed close count: " + failedCloseCounter.getValue() +
+                        //"; Total processed by this pool: " + totalOpenedCounter.getValue());
+
+                        throw e;
+                    }
+                }
+            }
+
+            ResourcePool.Manager manager = new PooledConnectionResourcePoolManager();
+
+            synchronized (fact)
+            {
+                fact.setMin( min );
+                fact.setMax( max );
+                fact.setStart( start );
+                fact.setIncrement( inc );
+                fact.setIdleResourceTestPeriod( idleConnectionTestPeriod * 1000);
+                fact.setResourceMaxIdleTime( maxIdleTime * 1000 );
+                fact.setExcessResourceMaxIdleTime( maxIdleTimeExcessConnections * 1000 );
+                fact.setResourceMaxAge( maxConnectionAge * 1000 );
+                fact.setExpirationEnforcementDelay( propertyCycle * 1000 );
+                fact.setDestroyOverdueResourceTime( unreturnedConnectionTimeout * 1000 );
+                fact.setDebugStoreCheckoutStackTrace( debugUnreturnedConnectionStackTraces );
+                fact.setAcquisitionRetryAttempts( acq_retry_attempts );
+                fact.setAcquisitionRetryDelay( acq_retry_delay );
+                fact.setBreakOnAcquisitionFailure( break_after_acq_failure );
+                rp = fact.createPool( manager );
+            }
+        }
+        catch (ResourcePoolException e)
+        { throw SqlUtils.toSQLException(e); }
+                    }
+
+    public PooledConnection checkoutPooledConnection() throws SQLException
+    { 
+        //System.err.println(this + " -- CHECKOUT");
+        try { return (PooledConnection) rp.checkoutResource( checkoutTimeout ); }
+        catch (TimeoutException e)
+        { throw SqlUtils.toSQLException("An attempt by a client to checkout a Connection has timed out.", e); }
+        catch (CannotAcquireResourceException e)
+        { throw SqlUtils.toSQLException("Connections could not be acquired from the underlying database!", "08001", e); }
+        catch (Exception e)
+        { throw SqlUtils.toSQLException(e); }
+    }
+
+    public void checkinPooledConnection(PooledConnection pcon) throws SQLException
+    { 
+        //System.err.println(this + " -- CHECKIN");
+        try { rp.checkinResource( pcon ); } 
+        catch (ResourcePoolException e)
+        { throw SqlUtils.toSQLException(e); }
+    }
+
+    public float getEffectivePropertyCycle() throws SQLException
+    {
+        try
+        { return rp.getEffectiveExpirationEnforcementDelay() / 1000f; }
+        catch (ResourcePoolException e)
+        { throw SqlUtils.toSQLException(e); }
+    }
+
+    public int getNumThreadsAwaitingCheckout() throws SQLException
+    {
+        try
+        { return rp.getNumCheckoutWaiters(); }
+        catch (ResourcePoolException e)
+        { throw SqlUtils.toSQLException(e); }
+    }
+
+    public int getStatementCacheNumStatements()
+    { return scache == null ? 0 : scache.getNumStatements(); }
+
+    public int getStatementCacheNumCheckedOut()
+    { return scache == null ? 0 : scache.getNumStatementsCheckedOut(); }
+
+    public int getStatementCacheNumConnectionsWithCachedStatements()
+    { return scache == null ? 0 : scache.getNumConnectionsWithCachedStatements(); }
+
+    public String dumpStatementCacheStatus()
+    { return scache == null ? "Statement caching disabled." : scache.dumpStatementCacheStatus(); }
+
+    public void close() throws SQLException
+    { close( true ); }
+
+    public void close( boolean close_outstanding_connections ) throws SQLException
+    { 
+        // System.err.println(this + " closing.");
+        Exception throwMe = null;
+
+        try { if (scache != null) scache.close(); }
+        catch (SQLException e)
+        { throwMe = e; }
+
+        try 
+        { rp.close( close_outstanding_connections ); }
+        catch (ResourcePoolException e)
+        {
+            if ( throwMe != null && logger.isLoggable( MLevel.WARNING ) )
+                logger.log( MLevel.WARNING, "An Exception occurred while closing the StatementCache.", throwMe);
+            throwMe = e; 
+        }
+
+        if (throwMe != null)
+            throw SqlUtils.toSQLException( throwMe );
+    }
+
+    class ConnectionEventListenerImpl implements ConnectionEventListener
+    {
+
+        //
+        // We might want to check Connections in asynchronously, 
+        // because this is called
+        // (indirectly) from a sync'ed method of NewPooledConnection, but
+        // NewPooledConnection may be closed synchronously from a sync'ed
+        // method of the resource pool, leading to a deadlock. Checking
+        // Connections in asynchronously breaks the cycle.
+        //
+        // But then we want checkins to happen quickly and reliably,
+        // whereas pool shutdowns are rare, so perhaps it's best to
+        // leave this synchronous, and let the closing of pooled
+        // resources on pool closes happen asynchronously to break
+        // the deadlock. 
+        //
+        // For now we're leaving both versions around, but with faster
+        // and more reliable synchronous checkin enabled, and async closing
+        // of resources in BasicResourcePool.close().
+        //
+        public void connectionClosed(final ConnectionEvent evt)
+        { 
+            //System.err.println("Checking in: " + evt.getSource());
+
+            if (ASYNCHRONOUS_CONNECTION_EVENT_LISTENER) 
+            {
+                Runnable r = new Runnable()
+                {
+                    public void run()
+                    { doCheckinResource( evt ); }
+                };
+                sharedTaskRunner.postRunnable( r );
+            }
+            else
+                doCheckinResource( evt );
+        }
+
+        private void doCheckinResource(ConnectionEvent evt)
+        {
+            try
+            { rp.checkinResource( evt.getSource() ); }
+            catch (Exception e)
+            { 
+                //e.printStackTrace(); 
+                logger.log( MLevel.WARNING, 
+                                "An Exception occurred while trying to check a PooledConection into a ResourcePool.",
+                                e );
+            }
+        }
+
+        //
+        // We might want to update the pool asynchronously, because this is called
+        // (indirectly) from a sync'ed method of NewPooledConnection, but
+        // NewPooledConnection may be closed synchronously from a sync'ed
+        // method of the resource pool, leading to a deadlock. Updating
+        // pool status asynchronously breaks the cycle.
+        //
+        // But then we want checkins to happen quickly and reliably,
+        // whereas pool shutdowns are rare, so perhaps it's best to
+        // leave all ConnectionEvent handling synchronous, and let the closing of pooled
+        // resources on pool closes happen asynchronously to break
+        // the deadlock. 
+        //
+        // For now we're leaving both versions around, but with faster
+        // and more reliable synchrounous ConnectionEventHandling enabled, and async closing
+        // of resources in BasicResourcePool.close().
+        //
+        public void connectionErrorOccurred(final ConnectionEvent evt)
+        {
+//          System.err.println("CONNECTION ERROR OCCURRED!");
+//          System.err.println();
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.fine("CONNECTION ERROR OCCURRED!");
+
+            final PooledConnection pc = (PooledConnection) evt.getSource();
+            int status;
+            if (pc instanceof C3P0PooledConnection)
+                status = ((C3P0PooledConnection) pc).getConnectionStatus();
+            else if (pc instanceof NewPooledConnection)
+                status = ((NewPooledConnection) pc).getConnectionStatus();
+            else //default to invalid connection, but not invalid database
+                status = ConnectionTester.CONNECTION_IS_INVALID;
+
+            final int final_status = status;
+
+            if (ASYNCHRONOUS_CONNECTION_EVENT_LISTENER) 
+            {
+                Runnable r = new Runnable()
+                {
+                    public void run()
+                    { doMarkPoolStatus( pc, final_status ); }
+                };
+                sharedTaskRunner.postRunnable( r );
+            }
+            else
+                doMarkPoolStatus( pc, final_status );
+        }
+
+        private void doMarkPoolStatus(PooledConnection pc, int status)
+        {
+            try
+            {
+                switch (status)
+                {
+                case ConnectionTester.CONNECTION_IS_OKAY:
+                    throw new RuntimeException("connectionErrorOcccurred() should only be " +
+                    "called for errors fatal to the Connection.");
+                case ConnectionTester.CONNECTION_IS_INVALID:
+                    rp.markBroken( pc );
+                    break;
+                case ConnectionTester.DATABASE_IS_INVALID:
+                    if (logger.isLoggable(MLevel.WARNING))
+                        logger.warning("A ConnectionTest has failed, reporting that all previously acquired Connections are likely invalid. " +
+                        "The pool will be reset.");
+                    rp.resetPool();
+                    break;
+                default:
+                    throw new RuntimeException("Bad Connection Tester (" + connectionTester + ") " +
+                                    "returned invalid status (" + status + ").");
+                }
+            }
+            catch ( ResourcePoolException e )
+            {
+                //System.err.println("Uh oh... our resource pool is probably broken!");
+                //e.printStackTrace();
+                logger.log(MLevel.WARNING, "Uh oh... our resource pool is probably broken!", e);
+            }
+        }
+    }
+
+    public int getNumConnections() throws SQLException
+    { 
+        try { return rp.getPoolSize(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public int getNumIdleConnections() throws SQLException
+    { 
+        try { return rp.getAvailableCount(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public int getNumBusyConnections() throws SQLException
+    { 
+        try 
+        {
+            synchronized ( rp )
+            { return (rp.getAwaitingCheckinCount() - rp.getExcludedCount()); }
+        }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public int getNumUnclosedOrphanedConnections() throws SQLException
+    {
+        try { return rp.getExcludedCount(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+    
+    public long getStartTime() throws SQLException
+    {
+        try { return rp.getStartTime(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public long getUpTime() throws SQLException
+    {
+        try { return rp.getUpTime(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public long getNumFailedCheckins() throws SQLException
+    {
+        try { return rp.getNumFailedCheckins(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public long getNumFailedCheckouts() throws SQLException
+    {
+        try { return rp.getNumFailedCheckouts(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public long getNumFailedIdleTests() throws SQLException
+    {
+        try { return rp.getNumFailedIdleTests(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public Throwable getLastCheckinFailure() throws SQLException
+    {
+        try { return rp.getLastCheckinFailure(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public Throwable getLastCheckoutFailure() throws SQLException
+    {
+        try { return rp.getLastCheckoutFailure(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public Throwable getLastIdleTestFailure() throws SQLException
+    {
+        try { return rp.getLastIdleCheckFailure(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    public Throwable getLastConnectionTestFailure() throws SQLException
+    {
+        try { return rp.getLastResourceTestFailure(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+    
+    public Throwable getLastAcquisitionFailure() throws SQLException
+    {
+        try { return rp.getLastAcquisitionFailure(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    /**
+     * Discards all Connections managed by the pool
+     * and reacquires new Connections to populate.
+     * Current checked out Connections will still
+     * be valid, and should still be checked into the
+     * pool (so the pool can destroy them).
+     */
+    public void reset() throws SQLException
+    { 
+        try { rp.resetPool(); }
+        catch ( Exception e )
+        { 
+            //e.printStackTrace();
+            logger.log( MLevel.WARNING, null, e );
+            throw SqlUtils.toSQLException( e );
+        }
+    }
+
+    final static class ThrowableHolderPool
+    {
+        LinkedList l = new LinkedList();
+
+        synchronized Throwable[] getThrowableHolder()
+        {
+            if (l.size() == 0)
+                return new Throwable[1];
+            else
+                return (Throwable[]) l.remove(0);
+        }
+
+        synchronized void returnThrowableHolder(Throwable[] th)
+        {
+            th[0] = null;
+            l.add(th);
+        }
+    }
+    
+}
diff --git a/src/classes/com/mchange/v2/c3p0/impl/C3P0PooledConnectionPoolManager.java b/src/classes/com/mchange/v2/c3p0/impl/C3P0PooledConnectionPoolManager.java
new file mode 100644
index 0000000..1819d81
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/C3P0PooledConnectionPoolManager.java
@@ -0,0 +1,1091 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.beans.*;
+import java.util.*;
+import java.lang.reflect.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v2.c3p0.cfg.*;
+import com.mchange.v2.async.*;
+import com.mchange.v2.coalesce.*;
+import com.mchange.v1.db.sql.*;
+import com.mchange.v2.log.*;
+import com.mchange.v1.lang.BooleanUtils;
+import com.mchange.v2.sql.SqlUtils;
+import com.mchange.v2.resourcepool.ResourcePoolFactory;
+import com.mchange.v2.resourcepool.BasicResourcePoolFactory;
+
+public final class C3P0PooledConnectionPoolManager
+{
+    private final static MLogger logger = MLog.getLogger( C3P0PooledConnectionPoolManager.class );
+
+    private final static boolean POOL_EVENT_SUPPORT = false;
+
+    private final static CoalesceChecker COALESCE_CHECKER = IdentityTokenizedCoalesceChecker.INSTANCE;
+
+    // unsync'ed coalescer -- we synchronize the static factory method that uses it
+    final static Coalescer COALESCER = CoalescerFactory.createCoalescer( COALESCE_CHECKER, true, false );
+
+    final static int DFLT_NUM_TASK_THREADS_PER_DATA_SOURCE = 3;
+
+    //MT: protected by this' lock
+    ThreadPoolAsynchronousRunner taskRunner;
+    Timer                        timer; 
+    ResourcePoolFactory          rpfact;
+    Map                          authsToPools;
+
+    /* MT: independently thread-safe, never reassigned post-ctor or factory */
+    final ConnectionPoolDataSource cpds;
+    final Map propNamesToReadMethods;
+    final Map flatPropertyOverrides;
+    final Map userOverrides;
+    final DbAuth defaultAuth;
+    final String parentDataSourceIdentityToken;
+    /* MT: end independently thread-safe, never reassigned post-ctor or factory */
+
+    /* MT: unchanging after constructor completes */
+    int num_task_threads = DFLT_NUM_TASK_THREADS_PER_DATA_SOURCE;
+
+    /* MT: end unchanging after constructor completes */
+
+    public int getThreadPoolSize()
+    { return taskRunner.getThreadCount(); }
+
+    public int getThreadPoolNumActiveThreads()
+    { return taskRunner.getActiveCount(); }
+
+    public int getThreadPoolNumIdleThreads()
+    { return taskRunner.getIdleCount(); }
+
+    public int getThreadPoolNumTasksPending()
+    { return taskRunner.getPendingTaskCount(); }
+
+    public String getThreadPoolStackTraces()
+    { return taskRunner.getStackTraces(); }
+
+    public String getThreadPoolStatus()
+    { return taskRunner.getStatus(); }
+
+    private synchronized void poolsInit()
+    {
+        this.timer = new Timer( true );
+
+        int matt = this.getMaxAdministrativeTaskTime( null );
+        if ( matt > 0 )
+        {
+            int matt_ms = matt * 1000;
+            this.taskRunner = new ThreadPoolAsynchronousRunner( num_task_threads, 
+
+                            true,        // daemon thread
+
+                            matt_ms,     // wait before interrupt()
+
+                            matt_ms * 3, // wait before deadlock declared if no tasks clear
+
+                            matt_ms * 6, // wait before deadlock tasks are interrupted (again)
+                            // after the hung thread has been cleared and replaced
+                            // (in hopes of getting the thread to terminate for
+                            // garbage collection)
+
+                            timer );
+        }
+        else
+            this.taskRunner = new ThreadPoolAsynchronousRunner( num_task_threads, true, timer );
+        //this.taskRunner = new RoundRobinAsynchronousRunner( num_task_threads, true );
+        //this.rpfact = ResourcePoolFactory.createInstance( taskRunner, timer );
+        if (POOL_EVENT_SUPPORT)
+            this.rpfact = ResourcePoolFactory.createInstance( taskRunner, null, timer );
+        else
+            this.rpfact = BasicResourcePoolFactory.createNoEventSupportInstance( taskRunner, timer );
+        this.authsToPools = new HashMap();
+    }
+
+    private void poolsDestroy()
+    { poolsDestroy( true ); }
+
+    private synchronized void poolsDestroy( boolean close_outstanding_connections )
+    {
+        //System.err.println("poolsDestroy() -- " + this);
+        for (Iterator ii = authsToPools.values().iterator(); ii.hasNext(); )
+        {
+            try
+            { ((C3P0PooledConnectionPool) ii.next()).close( close_outstanding_connections ); }
+            catch ( Exception e )
+            { 
+                //e.printStackTrace(); 
+                logger.log(MLevel.WARNING, "An Exception occurred while trying to clean up a pool!", e);
+            }
+        }
+
+        this.taskRunner.close( true );
+        this.timer.cancel();
+
+        this.taskRunner = null;
+        this.timer = null;
+        this.rpfact = null;
+        this.authsToPools = null;
+    }
+
+    public C3P0PooledConnectionPoolManager(ConnectionPoolDataSource cpds, 
+                    Map flatPropertyOverrides,     // Map of properties, usually null
+                    Map forceUserOverrides,        // userNames to Map of properties, usually null
+                    int num_task_threads,
+                    String parentDataSourceIdentityToken)
+    throws SQLException
+    {
+        try
+        {
+            this.cpds = cpds;
+            this.flatPropertyOverrides = flatPropertyOverrides;
+            this.num_task_threads = num_task_threads;
+            this.parentDataSourceIdentityToken = parentDataSourceIdentityToken;
+
+            DbAuth auth = null;
+
+            if ( flatPropertyOverrides != null )
+            {
+                String overrideUser     = (String) flatPropertyOverrides.get("overrideDefaultUser");
+                String overridePassword = (String) flatPropertyOverrides.get("overrideDefaultPassword");
+
+                if (overrideUser == null)
+                {
+                    overrideUser     = (String) flatPropertyOverrides.get("user");
+                    overridePassword = (String) flatPropertyOverrides.get("password");
+                }
+
+                if (overrideUser != null)
+                    auth = new DbAuth( overrideUser, overridePassword );
+            }
+
+            if (auth == null)
+                auth = C3P0ImplUtils.findAuth( cpds );
+
+            this.defaultAuth = auth;
+
+            Map tmp = new HashMap();
+            BeanInfo bi = Introspector.getBeanInfo( cpds.getClass() );
+            PropertyDescriptor[] pds = bi.getPropertyDescriptors();
+            PropertyDescriptor pd = null;
+            for (int i = 0, len = pds.length; i < len; ++i)
+            {
+                pd = pds[i];
+
+                String name = pd.getName();
+                Method m = pd.getReadMethod();
+
+                if (m != null)
+                    tmp.put( name, m );
+            }
+            this.propNamesToReadMethods = tmp;
+
+            if (forceUserOverrides == null)
+            {
+                Method uom = (Method) propNamesToReadMethods.get( "userOverridesAsString" );
+                if (uom != null)
+                {
+                    String uoas = (String) uom.invoke( cpds, null );
+                    //System.err.println("uoas: " + uoas);
+                    Map uo = C3P0ImplUtils.parseUserOverridesAsString( uoas );
+                    this.userOverrides = uo;
+                }
+                else
+                    this.userOverrides = Collections.EMPTY_MAP;
+            }
+            else
+                this.userOverrides = forceUserOverrides;
+
+            poolsInit();
+        }
+        catch (Exception e)
+        {
+            if (Debug.DEBUG)
+                logger.log(MLevel.FINE, null, e);
+            //e.printStackTrace();
+            throw SqlUtils.toSQLException(e);
+        }
+    }
+
+    public synchronized C3P0PooledConnectionPool getPool(String username, String password, boolean create) throws SQLException
+    {
+        if (create)
+            return getPool( username, password );
+        else
+        {
+            DbAuth checkAuth = new DbAuth( username, password );
+            C3P0PooledConnectionPool out = (C3P0PooledConnectionPool) authsToPools.get(checkAuth);
+            if (out == null)
+                throw new SQLException("No pool has been initialized for databse user '" + username + "' with the specified password.");
+            else
+                return out;
+        }
+    }
+
+    public C3P0PooledConnectionPool getPool(String username, String password)
+    throws SQLException
+    { return getPool( new DbAuth( username, password ) ); }
+
+    public synchronized C3P0PooledConnectionPool getPool(DbAuth auth)
+    throws SQLException
+    {
+        C3P0PooledConnectionPool out = (C3P0PooledConnectionPool) authsToPools.get(auth);
+        if (out == null)
+        {
+            out = createPooledConnectionPool(auth);
+            authsToPools.put( auth, out );
+        }
+        return out;
+    }
+
+    public synchronized Set getManagedAuths()
+    { return Collections.unmodifiableSet( authsToPools.keySet() ); }
+
+    public synchronized int getNumManagedAuths()
+    { return authsToPools.size(); }
+
+    public C3P0PooledConnectionPool getPool()
+    throws SQLException
+    { return getPool( defaultAuth ); }
+
+    public synchronized int getNumIdleConnectionsAllAuths() throws SQLException
+    {
+        int out = 0;
+        for (Iterator ii = authsToPools.values().iterator(); ii.hasNext(); )
+            out += ((C3P0PooledConnectionPool) ii.next()).getNumIdleConnections();
+        return out;
+    }
+
+    public synchronized int getNumBusyConnectionsAllAuths() throws SQLException
+    {
+        int out = 0;
+        for (Iterator ii = authsToPools.values().iterator(); ii.hasNext(); )
+            out += ((C3P0PooledConnectionPool) ii.next()).getNumBusyConnections();
+        return out;
+    }
+
+    public synchronized int getNumConnectionsAllAuths() throws SQLException
+    {
+        int out = 0;
+        for (Iterator ii = authsToPools.values().iterator(); ii.hasNext(); )
+            out += ((C3P0PooledConnectionPool) ii.next()).getNumConnections();
+        return out;
+    }
+
+    public synchronized int getNumUnclosedOrphanedConnectionsAllAuths() throws SQLException
+    {
+        int out = 0;
+        for (Iterator ii = authsToPools.values().iterator(); ii.hasNext(); )
+            out += ((C3P0PooledConnectionPool) ii.next()).getNumUnclosedOrphanedConnections();
+        return out;
+    }
+
+    public synchronized int getStatementCacheNumStatementsAllUsers() throws SQLException
+    {
+        int out = 0;
+        for (Iterator ii = authsToPools.values().iterator(); ii.hasNext(); )
+            out += ((C3P0PooledConnectionPool) ii.next()).getStatementCacheNumStatements();
+        return out;
+    }
+
+    public synchronized int getStatementCacheNumCheckedOutStatementsAllUsers() throws SQLException
+    {
+        int out = 0;
+        for (Iterator ii = authsToPools.values().iterator(); ii.hasNext(); )
+            out += ((C3P0PooledConnectionPool) ii.next()).getStatementCacheNumCheckedOut();
+        return out;
+    }
+
+    public synchronized int getStatementCacheNumConnectionsWithCachedStatementsAllUsers() throws SQLException
+    {
+        int out = 0;
+        for (Iterator ii = authsToPools.values().iterator(); ii.hasNext(); )
+            out += ((C3P0PooledConnectionPool) ii.next()).getStatementCacheNumConnectionsWithCachedStatements();
+        return out;
+    }
+
+    public synchronized void softResetAllAuths() throws SQLException
+    {
+        for (Iterator ii = authsToPools.values().iterator(); ii.hasNext(); )
+            ((C3P0PooledConnectionPool) ii.next()).reset();
+    }
+
+    public void close()
+    { this.close( true ); }
+
+    public synchronized void close( boolean close_outstanding_connections )
+    {
+        // System.err.println("close()ing " + this);
+        if (authsToPools != null)
+            poolsDestroy( close_outstanding_connections );
+    }
+
+    protected synchronized void finalize()
+    {
+        // System.err.println("finalizing... " + this);
+        this.close();
+    }
+
+    private Object getObject(String propName, String userName)
+    {
+        Object out = null;
+
+        if (userName != null)
+        {
+            //userOverrides are usually config file defined, unless rarely used forceUserOverrides is supplied!
+            Map specificUserOverrides = (Map) userOverrides.get( userName ); 
+            if (specificUserOverrides != null)
+                out = specificUserOverrides.get( propName );
+        }
+
+        if (out == null && flatPropertyOverrides != null) //flatPropertyOverrides is a rarely used mechanism for forcing a config
+            out = flatPropertyOverrides.get( propName );
+
+        //if the ConnectionPoolDataSource has config parameter defined as a property use it 
+        //(unless there was a user-specific or force override found above)
+        if (out == null) 
+        {
+            try
+            {
+                Method m = (Method) propNamesToReadMethods.get( propName );
+                if (m != null)
+                {
+                    Object readProp = m.invoke( cpds, null );
+                    if (readProp != null)
+                        out = readProp.toString();
+                }
+            }
+            catch (Exception e)
+            {
+                if (logger.isLoggable( MLevel.WARNING ))
+                    logger.log(MLevel.WARNING, 
+                                    "An exception occurred while trying to read property '" + propName + 
+                                    "' from ConnectionPoolDataSource: " + cpds +
+                                    ". Default config value will be used.",
+                                    e );
+            }
+        }
+
+        //if the ConnectionPoolDataSource DID NOT have config parameter defined as a property
+        //(and there was no user-specific or force override)
+        //use config-defined default
+        if (out == null)
+            out = C3P0Config.getUnspecifiedUserProperty( propName, null );
+
+        return out;
+    }
+
+    private String getString(String propName, String userName)
+    {
+        Object o = getObject( propName,  userName);
+        return (o == null ? null : o.toString());
+    }
+
+    private int getInt(String propName, String userName) throws Exception
+    {
+        Object o = getObject( propName,  userName);
+        if (o instanceof Integer)
+            return ((Integer) o).intValue();
+        else if (o instanceof String)
+            return Integer.parseInt( (String) o );
+        else
+            throw new Exception("Unexpected object found for putative int property '" + propName +"': " + o);
+    }
+
+    private boolean getBoolean(String propName, String userName) throws Exception
+    {
+        Object o = getObject( propName,  userName);
+        if (o instanceof Boolean)
+            return ((Boolean) o).booleanValue();
+        else if (o instanceof String)
+            return BooleanUtils.parseBoolean( (String) o );
+        else
+            throw new Exception("Unexpected object found for putative boolean property '" + propName +"': " + o);
+    }
+
+    public String getAutomaticTestTable(String userName)
+    { return getString("automaticTestTable", userName ); }
+
+    public String getPreferredTestQuery(String userName)
+    { return getString("preferredTestQuery", userName ); }
+
+    private int getInitialPoolSize(String userName)
+    {
+        try
+        { return getInt("initialPoolSize", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.initialPoolSize();
+        }
+    }
+
+    public int getMinPoolSize(String userName)
+    {
+        try
+        { return getInt("minPoolSize", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.minPoolSize();
+        }
+    }
+
+    private int getMaxPoolSize(String userName)
+    {
+        try
+        { return getInt("maxPoolSize", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.maxPoolSize();
+        }
+    }
+
+    private int getMaxStatements(String userName)
+    {
+        try
+        { return getInt("maxStatements", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.maxStatements();
+        }
+    }
+
+    private int getMaxStatementsPerConnection(String userName)
+    {
+        try
+        { return getInt("maxStatementsPerConnection", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.maxStatementsPerConnection();
+        }
+    }
+
+    private int getAcquireIncrement(String userName)
+    {
+        try
+        { return getInt("acquireIncrement", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.acquireIncrement();
+        }
+    }
+
+    private int getAcquireRetryAttempts(String userName)
+    {
+        try
+        { return getInt("acquireRetryAttempts", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.acquireRetryAttempts();
+        }
+    }
+
+    private int getAcquireRetryDelay(String userName)
+    {
+        try
+        { return getInt("acquireRetryDelay", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.acquireRetryDelay();
+        }
+    }
+
+    private boolean getBreakAfterAcquireFailure(String userName)
+    {
+        try
+        { return getBoolean("breakAfterAcquireFailure", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch boolean property", e);
+            return C3P0Defaults.breakAfterAcquireFailure();
+        }
+    }
+
+    private int getCheckoutTimeout(String userName)
+    {
+        try
+        { return getInt("checkoutTimeout", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.checkoutTimeout();
+        }
+    }
+
+    private int getIdleConnectionTestPeriod(String userName)
+    {
+        try
+        { return getInt("idleConnectionTestPeriod", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.idleConnectionTestPeriod();
+        }
+    }
+
+    private int getMaxIdleTime(String userName)
+    {
+        try
+        { return getInt("maxIdleTime", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.maxIdleTime();
+        }
+    }
+
+    private int getUnreturnedConnectionTimeout(String userName)
+    {
+        try
+        { return getInt("unreturnedConnectionTimeout", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.unreturnedConnectionTimeout();
+        }
+    }
+
+    private boolean getTestConnectionOnCheckout(String userName)
+    {
+        try
+        { return getBoolean("testConnectionOnCheckout", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch boolean property", e);
+            return C3P0Defaults.testConnectionOnCheckout();
+        }
+    }
+
+    private boolean getTestConnectionOnCheckin(String userName)
+    {
+        try
+        { return getBoolean("testConnectionOnCheckin", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch boolean property", e);
+            return C3P0Defaults.testConnectionOnCheckin();
+        }
+    }
+
+    private boolean getDebugUnreturnedConnectionStackTraces(String userName)
+    {
+        try
+        { return getBoolean("debugUnreturnedConnectionStackTraces", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch boolean property", e);
+            return C3P0Defaults.debugUnreturnedConnectionStackTraces();
+        }
+    }
+
+    private String getConnectionTesterClassName(String userName)
+    { return getString("connectionTesterClassName", userName ); }
+
+    private ConnectionTester getConnectionTester(String userName)
+    { return C3P0Registry.getConnectionTester( getConnectionTesterClassName( userName ) ); }
+
+    private String getConnectionCustomizerClassName(String userName)
+    { return getString("connectionCustomizerClassName", userName ); }
+
+    private ConnectionCustomizer getConnectionCustomizer(String userName) throws SQLException
+    { return C3P0Registry.getConnectionCustomizer( getConnectionCustomizerClassName( userName ) ); }
+
+    private int getMaxIdleTimeExcessConnections(String userName)
+    {
+        try
+        { return getInt("maxIdleTimeExcessConnections", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.maxIdleTimeExcessConnections();
+        }
+    }
+
+    private int getMaxAdministrativeTaskTime(String userName)
+    {
+        try
+        { return getInt("maxAdministrativeTaskTime", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.maxAdministrativeTaskTime();
+        }
+    }
+
+    private int getMaxConnectionAge(String userName)
+    {
+        try
+        { return getInt("maxConnectionAge", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.maxConnectionAge();
+        }
+    }
+
+    private int getPropertyCycle(String userName)
+    {
+        try
+        { return getInt("propertyCycle", userName ); }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.log( MLevel.FINE, "Could not fetch int property", e);
+            return C3P0Defaults.propertyCycle();
+        }
+    }
+
+
+    // called only from sync'ed methods
+    private C3P0PooledConnectionPool createPooledConnectionPool(DbAuth auth) throws SQLException
+    {
+        String userName = auth.getUser();
+        String automaticTestTable = getAutomaticTestTable( userName );
+        String realTestQuery;
+
+        if (automaticTestTable != null)
+        {
+            realTestQuery = initializeAutomaticTestTable( automaticTestTable, auth );
+            if (this.getPreferredTestQuery( userName ) != null)
+            {
+                if ( logger.isLoggable( MLevel.WARNING ) )
+                {
+                    logger.logp(MLevel.WARNING, 
+                                    C3P0PooledConnectionPoolManager.class.getName(),
+                                    "createPooledConnectionPool",
+                                    "[c3p0] Both automaticTestTable and preferredTestQuery have been set! " +
+                                    "Using automaticTestTable, and ignoring preferredTestQuery. Real test query is ''{0}''.",
+                                    realTestQuery
+                    );
+                }
+            }
+        }
+        else
+        {
+	    // when there is an automaticTestTable to be constructed, we
+	    // have little choice but to grab a Connection on initialization
+	    // to ensure that the table exists before the pool tries to
+	    // test Connections. in c3p0-0.9.1-pre10, i added the check below
+	    // to grab and destroy a cxn even when we don't
+	    // need one, to ensure that db access params are correct before
+	    // we start up a pool. a user who frequently creates and destroys
+	    // PooledDataSources complained about the extra initialization
+	    // time. the main use of this test was to prevent superfluous
+	    // bad pools from being intialized when JMX users type bad
+	    // authentification information into a query method. This is
+	    // now prevented in AbstractPoolBackedDataSource. Still, it is
+	    // easy for clients to start pools uselessly by asking for
+	    // Connections with bad authentification information. We adopt
+	    // the compromise position of "trusting" the DataSource's default
+	    // authentification info (as defined by defaultAuth), but ensuring
+	    // that authentification succeeds via the check below when non-default
+	    // authentification info is provided.
+
+	    if (! defaultAuth.equals( auth ))
+		ensureFirstConnectionAcquisition( auth );
+
+            realTestQuery = this.getPreferredTestQuery( userName );
+        }
+
+        C3P0PooledConnectionPool out =  new C3P0PooledConnectionPool( cpds,
+                        auth,
+                        this.getMinPoolSize( userName ),
+                        this.getMaxPoolSize( userName ),
+                        this.getInitialPoolSize( userName ),
+                        this.getAcquireIncrement( userName ),
+                        this.getAcquireRetryAttempts( userName ),
+                        this.getAcquireRetryDelay( userName ),
+                        this.getBreakAfterAcquireFailure( userName ),
+                        this.getCheckoutTimeout( userName ),
+                        this.getIdleConnectionTestPeriod( userName ),
+                        this.getMaxIdleTime( userName ),
+                        this.getMaxIdleTimeExcessConnections( userName ),
+                        this.getMaxConnectionAge( userName ),
+                        this.getPropertyCycle( userName ),
+                        this.getUnreturnedConnectionTimeout( userName ),
+                        this.getDebugUnreturnedConnectionStackTraces( userName ),
+                        this.getTestConnectionOnCheckout( userName ),
+                        this.getTestConnectionOnCheckin( userName ),
+                        this.getMaxStatements( userName ),
+                        this.getMaxStatementsPerConnection( userName ),
+                        this.getConnectionTester( userName ),
+                        this.getConnectionCustomizer( userName ),
+                        realTestQuery,
+                        rpfact,
+                        taskRunner,
+                        parentDataSourceIdentityToken );
+        return out;
+    }
+
+
+    // only called from sync'ed methods
+    private String initializeAutomaticTestTable(String automaticTestTable, DbAuth auth) throws SQLException
+    {
+        PooledConnection throwawayPooledConnection =    auth.equals( defaultAuth ) ? 
+                                                        cpds.getPooledConnection() : 
+                                                        cpds.getPooledConnection(auth.getUser(), auth.getPassword()); 
+        Connection c = null;
+        PreparedStatement testStmt = null;
+        PreparedStatement createStmt = null;
+        ResultSet mdrs = null;
+        ResultSet rs = null;
+        boolean exists;
+        boolean has_rows;
+        String out;
+        try
+        {
+            c = throwawayPooledConnection.getConnection();
+
+            DatabaseMetaData dmd = c.getMetaData();
+            String q = dmd.getIdentifierQuoteString();
+            String quotedTableName = q + automaticTestTable + q;
+            out = "SELECT * FROM " + quotedTableName;
+            mdrs = dmd.getTables( null, null, automaticTestTable, new String[] {"TABLE"} );
+            exists = mdrs.next();
+
+            //System.err.println("Table " + automaticTestTable + " exists? " + exists);
+
+            if (exists)
+            {
+                testStmt = c.prepareStatement( out );
+                rs = testStmt.executeQuery();
+                has_rows = rs.next();
+                if (has_rows)
+                    throw new SQLException("automatic test table '" + automaticTestTable + 
+                                    "' contains rows, and it should not! Please set this " +
+                                    "parameter to the name of a table c3p0 can create on its own, " +
+                    "that is not used elsewhere in the database!");
+            }
+            else
+            {
+                String createSql = "CREATE TABLE " + quotedTableName + " ( a CHAR(1) )";
+                try
+                {
+                    createStmt = c.prepareStatement( createSql );
+                    createStmt.executeUpdate();
+                }
+                catch (SQLException e)
+                {
+                    if (logger.isLoggable( MLevel.WARNING ))
+                        logger.log(MLevel.WARNING, 
+                                        "An attempt to create an automatic test table failed. Create SQL: " +
+                                        createSql,
+                                        e );
+                    throw e;
+                }
+            }
+            return out;
+        }
+        finally
+        { 
+            ResultSetUtils.attemptClose( mdrs );
+            ResultSetUtils.attemptClose( rs );
+            StatementUtils.attemptClose( testStmt );
+            StatementUtils.attemptClose( createStmt );
+            ConnectionUtils.attemptClose( c ); 
+            try{ if (throwawayPooledConnection != null) throwawayPooledConnection.close(); }
+            catch ( Exception e ) 
+            { 
+                //e.printStackTrace(); 
+                logger.log(MLevel.WARNING, "A PooledConnection failed to close.", e);
+            }
+        }
+    }
+    
+    private void ensureFirstConnectionAcquisition(DbAuth auth) throws SQLException
+    {
+        PooledConnection throwawayPooledConnection =    auth.equals( defaultAuth ) ? 
+                                                        cpds.getPooledConnection() : 
+                                                        cpds.getPooledConnection(auth.getUser(), auth.getPassword()); 
+        Connection c = null;
+        try
+        {
+            c = throwawayPooledConnection.getConnection();
+        }
+        finally
+        { 
+            ConnectionUtils.attemptClose( c ); 
+            try{ if (throwawayPooledConnection != null) throwawayPooledConnection.close(); }
+            catch ( Exception e ) 
+            { 
+                //e.printStackTrace(); 
+                logger.log(MLevel.WARNING, "A PooledConnection failed to close.", e);
+            }
+        }
+    }    
+}
+
+
+
+
+
+//public static find(ConnectionPoolDataSource cpds,
+//DbAuth defaultAuth,      //may be null
+//int maxStatements,
+//int minPoolSize,
+//int maxPoolSize,
+//int idleConnectionTestPeriod,
+//int maxIdleTime,
+//int acquireIncrement,
+//boolean testConnectionOnCheckout,
+//boolean autoCommitOnClose,
+//boolean forceIgnoreUnresolvedTransactions,
+//ConnectionTester connectionTester)
+//{
+//C3P0PooledConnectionPoolManager nascent = new C3P0PooledConnectionPoolManager( cpds,
+//defaultAuth,  
+//maxStatements,
+//minPoolSize,
+//maxPoolSize,
+//idleConnectionTestPeriod,
+//maxIdleTime,
+//acquireIncrement,
+//testConnectionOnCheckout,
+//autoCommitOnClose,
+//forceIgnoreUnresolvedTransactions,
+//connectionTester);
+//C3P0PooledConnectionPoolManager out = (C3P0PooledConnectionPoolManager) coalescer.coalesce( nascent );
+//if ( out == nascent ) //the new guy is the ONE
+//out.poolInit();
+//return out;
+//}
+
+//private C3P0PooledConnectionPoolManager(ConnectionPoolDataSource cpds,
+//DbAuth defaultAuth,      //may be null
+//int maxStatements,
+//int minPoolSize,
+//int maxPoolSize,
+//int idleConnectionTestPeriod,
+//int maxIdleTime,
+//int acquireIncrement,
+//boolean testConnectionOnCheckout,
+//boolean autoCommitOnClose,
+//boolean forceIgnoreUnresolvedTransactions,
+//ConnectionTester connectionTester)
+//{
+//this.cpds = cpds;
+//this.defaultAuth = (defaultAuth == null ? C3P0ImplUtils.NULL_AUTH : defaultAuth);
+//this.maxStatements = maxStatements;
+//this.minPoolSize = minPoolSize;
+//this.maxPoolSize = maxPoolSize;
+//this.idleConnectionTestPeriod = idleConnectionTestPeriod;
+//this.maxIdleTime = maxIdleTime;
+//this.acquireIncrement = acquireIncrement;
+//this.testConnectionOnCheckout = testConnectionOnCheckout;
+//this.autoCommitOnClose = autoCommitOnClose;
+//this.testConnectionOnCheckout = testConnectionOnCheckout;
+//this.forceIgnoreUnresolvedTransactions = forceIgnoreUnresolvedTransactions;
+//}
+
+//private final static CoalesceChecker COALESCE_CHECKER = new CoalesceChecker()
+//{
+//// note that we expect all ConnectionTesters of a single class to be effectively
+//// equivalent, since they are to be constructed via a no-arg ctor and no
+//// extra initialization is performed. thus we only compare the classes of ConnectionTesters.
+//public boolean checkCoalesce( Object a, Object b )
+//{
+//C3P0PooledConnectionPoolManager aa = (C3P0PooledConnectionPoolManager) a;
+//C3P0PooledConnectionPoolManager bb = (C3P0PooledConnectionPoolManager) b;
+
+//return
+//aa.poolOwnerIdentityToken.equals( bb.poolOwnerIdentityToken ) &&
+//(aa.preferredTestQuery == null ? (bb.preferredTestQuery == null ) : (aa.preferredTestQuery.equals( bb.preferredTestQuery ))) &&
+//(aa.automaticTestTable == null ? (bb.automaticTestTable == null ) : (aa.automaticTestTable.equals( bb.automaticTestTable ))) &&
+//aa.sourceCpdsIdentityToken.equals( bb.sourceCpdsIdentityToken ) &&
+//aa.num_task_threads == bb.num_task_threads &&
+//aa.maxStatements == bb.maxStatements &&
+//aa.maxStatementsPerConnection == bb.maxStatementsPerConnection &&
+//aa.minPoolSize == bb.minPoolSize &&
+//aa.idleConnectionTestPeriod == bb.idleConnectionTestPeriod &&
+//aa.maxIdleTime == bb.maxIdleTime &&
+//aa.checkoutTimeout == bb.checkoutTimeout &&
+//aa.acquireIncrement == bb.acquireIncrement &&
+//aa.acquireRetryAttempts == bb.acquireRetryAttempts &&
+//aa.acquireRetryDelay == bb.acquireRetryDelay &&
+//aa.breakAfterAcquireFailure == bb.breakAfterAcquireFailure &&
+//aa.testConnectionOnCheckout == bb.testConnectionOnCheckout &&
+//aa.testConnectionOnCheckin == bb.testConnectionOnCheckin &&
+//aa.autoCommitOnClose == bb.autoCommitOnClose &&
+//aa.forceIgnoreUnresolvedTransactions == bb.forceIgnoreUnresolvedTransactions &&
+//aa.defaultAuth.equals( bb.defaultAuth ) &&
+//aa.connectionTester.getClass().equals( bb.connectionTester.getClass() );
+//};
+
+//public int coalesceHash( Object a )
+//{
+//C3P0PooledConnectionPoolManager aa = (C3P0PooledConnectionPoolManager) a;
+//int out =
+//aa.poolOwnerIdentityToken.hashCode() ^
+//(aa.preferredTestQuery == null ? 0 : aa.preferredTestQuery.hashCode()) ^
+//(aa.automaticTestTable == null ? 0 : aa.automaticTestTable.hashCode()) ^
+//aa.sourceCpdsIdentityToken.hashCode() ^
+//aa.num_task_threads ^
+//aa.maxStatements ^
+//aa.maxStatementsPerConnection ^
+//aa.minPoolSize ^
+//aa.idleConnectionTestPeriod ^
+//aa.maxIdleTime ^
+//aa.checkoutTimeout ^
+//aa.acquireIncrement ^
+//aa.acquireRetryAttempts ^
+//aa.acquireRetryDelay ^
+//(aa.testConnectionOnCheckout          ? 1<<0 : 0) ^
+//(aa.testConnectionOnCheckin           ? 1<<1 : 0) ^
+//(aa.autoCommitOnClose                 ? 1<<2 : 0) ^
+//(aa.forceIgnoreUnresolvedTransactions ? 1<<3 : 0) ^
+//(aa.breakAfterAcquireFailure          ? 1<<4 : 0) ^
+//aa.defaultAuth.hashCode() ^
+//aa.connectionTester.getClass().hashCode(); 
+////System.err.println("coalesceHash() --> " + out);
+//return out;
+//};
+//};
+
+//int maxStatements                          = PoolConfig.defaultMaxStatements(); 
+//int maxStatementsPerConnection             = PoolConfig.defaultMaxStatementsPerConnection(); 
+//int minPoolSize                            = PoolConfig.defaultMinPoolSize();  
+//int maxPoolSize                            = PoolConfig.defaultMaxPoolSize();  
+//int idleConnectionTestPeriod               = PoolConfig.defaultIdleConnectionTestPeriod();
+//int maxIdleTime                            = PoolConfig.defaultMaxIdleTime();  
+//int checkoutTimeout                        = PoolConfig.defaultCheckoutTimeout(); 
+//int acquireIncrement                       = PoolConfig.defaultAcquireIncrement(); 
+//int acquireRetryAttempts                   = PoolConfig.defaultAcquireRetryAttempts(); 
+//int acquireRetryDelay                      = PoolConfig.defaultAcquireRetryDelay(); 
+//boolean breakAfterAcquireFailure           = PoolConfig.defaultBreakAfterAcquireFailure(); 
+//boolean testConnectionOnCheckout           = PoolConfig.defaultTestConnectionOnCheckout(); 
+//boolean testConnectionOnCheckin            = PoolConfig.defaultTestConnectionOnCheckin(); 
+//boolean autoCommitOnClose                  = PoolConfig.defaultAutoCommitOnClose(); 
+//boolean forceIgnoreUnresolvedTransactions  = PoolConfig.defaultForceIgnoreUnresolvedTransactions(); 
+//String preferredTestQuery                  = PoolConfig.defaultPreferredTestQuery(); 
+//String automaticTestTable                  = PoolConfig.defaultAutomaticTestTable(); 
+//DbAuth defaultAuth                         = C3P0ImplUtils.NULL_AUTH; 
+//ConnectionTester connectionTester          = C3P0ImplUtils.defaultConnectionTester();;
+
+
+//// we look for non-standard props user and
+//// password, available as read-only props on
+//// our implementation of ConnectionPoolDataSource.
+////
+//// If other implementations are used, the only
+//// hazard is the possibility that there will be 
+//// two pools for the same real authorization credentials
+//// one for when the credentials are explicitly specified,
+//// and one for when the defaults are used.
+
+//this.defaultAuth = C3P0ImplUtils.findAuth( cpds );
+
+//BeanInfo bi = Introspector.getBeanInfo( cpds.getClass() );
+//PropertyDescriptor[] pds = bi.getPropertyDescriptors();
+//for (int i = 0, len = pds.length; i < len; ++i)
+//{
+//PropertyDescriptor pd = pds[i];
+//Class propCl = pd.getPropertyType();
+//String propName = pd.getName();
+//Method readMethod = pd.getReadMethod();
+//Object propVal;
+//if (propCl == int.class)
+//{
+//propVal = readMethod.invoke( cpds, C3P0ImplUtils.NOARGS );
+//int value = ((Integer) propVal).intValue();
+//if ("maxStatements".equals(propName))
+//this.maxStatements = value;
+//else if ("maxStatementsPerConnection".equals(propName))
+//this.maxStatementsPerConnection = value;
+//else if ("minPoolSize".equals(propName))
+//this.minPoolSize = value;
+//else if ("maxPoolSize".equals(propName))
+//this.maxPoolSize = value;
+//else if ("idleConnectionTestPeriod".equals(propName))
+//this.idleConnectionTestPeriod = value;
+//else if ("maxIdleTime".equals(propName))
+//this.maxIdleTime = value;
+//else if ("checkoutTimeout".equals(propName))
+//this.checkoutTimeout = value;
+//else if ("acquireIncrement".equals(propName))
+//this.acquireIncrement = value;
+//else if ("acquireRetryAttempts".equals(propName))
+//this.acquireRetryAttempts = value;
+//else if ("acquireRetryDelay".equals(propName))
+//this.acquireRetryDelay = value;
+//// System.err.println( propName + " -> " + propVal );
+//}
+//else if (propCl == String.class)
+//{
+//propVal = readMethod.invoke( cpds, C3P0ImplUtils.NOARGS );
+//String value = (String) propVal;
+//if ("connectionTesterClassName".equals(propName))
+//this.connectionTester =
+//(ConnectionTester) Class.forName( value ).newInstance();
+//else if ("preferredTestQuery".equals(propName))
+//this.preferredTestQuery = value;
+//else if ("automaticTestTable".equals(propName))
+//this.automaticTestTable = value;
+//// System.err.println( propName + " -> " + propVal );
+//}
+//else if (propCl == boolean.class)
+//{
+//propVal = readMethod.invoke( cpds, C3P0ImplUtils.NOARGS );
+//boolean value = ((Boolean) propVal).booleanValue();
+//if ("testConnectionOnCheckout".equals(propName))
+//this.testConnectionOnCheckout = value;
+//else if ("testConnectionOnCheckin".equals(propName))
+//this.testConnectionOnCheckin = value;
+//else if ("autoCommitOnClose".equals(propName))
+//this.autoCommitOnClose = value;
+//else if ("forceIgnoreUnresolvedTransactions".equals(propName))
+//this.forceIgnoreUnresolvedTransactions = value;
+//else if ("breakAfterAcquireFailure".equals(propName))
+//this.breakAfterAcquireFailure = value;
+//// System.err.println( propName + " -> " + propVal );
+//}
+
+//}
+
diff --git a/src/classes/com/mchange/v2/c3p0/impl/DbAuth.java b/src/classes/com/mchange/v2/c3p0/impl/DbAuth.java
new file mode 100644
index 0000000..3d17022
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/DbAuth.java
@@ -0,0 +1,98 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.io.*;
+import com.mchange.v2.lang.ObjectUtils;
+import com.mchange.v2.ser.UnsupportedVersionException;
+
+public final class DbAuth implements Serializable
+{
+    transient String username;
+    transient String password;
+
+    public DbAuth(String username, String password)
+    {
+	this.username = username;
+	this.password = password;
+    }
+
+    public String getUser()
+    { return username; }
+
+    public String getPassword()
+    { return password; }
+
+    public boolean equals(Object o)
+    {
+	if (this == o)
+	    return true;
+	else if (o != null && this.getClass() == o.getClass())
+	    {
+		DbAuth other = (DbAuth) o;
+		return 
+		    ObjectUtils.eqOrBothNull(this.username, other.username) &&
+		    ObjectUtils.eqOrBothNull(this.password, other.password);
+	    }
+	else
+	    return false;
+    }
+
+    public int hashCode()
+    { 
+	return 
+	    ObjectUtils.hashOrZero(username) ^ 
+	    ObjectUtils.hashOrZero(password); 
+    }
+
+    //Serialization
+    static final long serialVersionUID = 1; //override to take control of versioning
+    private final static short VERSION = 0x0001;
+    
+    private void writeObject(ObjectOutputStream out) throws IOException
+    {
+	out.writeShort(VERSION);
+	out.writeObject(username); //may be null
+	out.writeObject(password); //may be null
+    }
+    
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
+    {
+	short version = in.readShort();
+	switch (version)
+	    {
+	    case 0x0001:
+		this.username = (String) in.readObject();
+		this.password = (String) in.readObject();
+		break;
+	    default:
+		throw new UnsupportedVersionException(this, version);
+	    }
+    }
+}
+								
+
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/impl/DefaultConnectionTester.java b/src/classes/com/mchange/v2/c3p0/impl/DefaultConnectionTester.java
new file mode 100644
index 0000000..3a00d4c
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/DefaultConnectionTester.java
@@ -0,0 +1,237 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.sql.*;
+import java.util.*;
+import com.mchange.v2.log.*;
+import com.mchange.v2.c3p0.AbstractConnectionTester;
+import com.mchange.v2.c3p0.FullQueryConnectionTester;
+import com.mchange.v1.db.sql.ResultSetUtils;
+import com.mchange.v1.db.sql.StatementUtils;
+
+public class DefaultConnectionTester extends AbstractConnectionTester
+{
+    final static MLogger logger = MLog.getLogger( DefaultConnectionTester.class );
+
+    final static int HASH_CODE = DefaultConnectionTester.class.getName().hashCode();
+
+    final static Set INVALID_DB_STATES;
+
+    static
+    {
+        Set temp = new HashSet();
+        temp.add("08001"); //SQL State "Unable to connect to data source"
+        temp.add("08007"); //SQL State "Connection failure during transaction"
+
+        // MySql appently uses this state to indicate a stale, expired
+        // connection when the database is fine, so we'll not presume
+        // this SQL state signals an invalid database.
+        //temp.add("08S01"); //SQL State "Communication link failure"
+
+        INVALID_DB_STATES = Collections.unmodifiableSet( temp );
+    }
+    
+    public int activeCheckConnection(Connection c, String query, Throwable[] rootCauseOutParamHolder)
+    {
+//      if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ) )
+//      logger.finer("Entering DefaultConnectionTester.activeCheckConnection(Connection c, String query). [query=" + query + "]");
+
+        if (query == null)
+            return activeCheckConnectionNoQuery( c, rootCauseOutParamHolder);
+        else
+        {
+            Statement stmt = null;
+            ResultSet rs   = null;
+            try
+            { 
+                //if (Math.random() < 0.1)
+                //    throw new NullPointerException("Test.");
+                
+                stmt = c.createStatement();
+                rs = stmt.executeQuery( query );
+                //rs.next();
+                return CONNECTION_IS_OKAY;
+            }
+            catch (SQLException e)
+            { 
+                if (Debug.DEBUG && logger.isLoggable( MLevel.FINE ) )
+                    logger.log( MLevel.FINE, "Connection " + c + " failed Connection test with an Exception! [query=" + query + "]", e );
+                
+                if (rootCauseOutParamHolder != null)
+                    rootCauseOutParamHolder[0] = e;
+
+                String state = e.getSQLState();
+                if ( INVALID_DB_STATES.contains( state ) )
+                {
+                    if (logger.isLoggable(MLevel.WARNING))
+                        logger.log(MLevel.WARNING,
+                                        "SQL State '" + state + 
+                                        "' of Exception which occurred during a Connection test (test with query '" + query + 
+                                        "') implies that the database is invalid, " + 
+                                        "and the pool should refill itself with fresh Connections.", e);
+                    return DATABASE_IS_INVALID;
+                }
+                else
+                    return CONNECTION_IS_INVALID; 
+            }
+            catch (Exception e)
+            {
+                if ( Debug.DEBUG && logger.isLoggable( MLevel.FINE ))
+                    logger.log( MLevel.FINE, "Connection " + c + " failed Connection test with an Exception!", e );
+
+                if (rootCauseOutParamHolder != null)
+                    rootCauseOutParamHolder[0] = e;
+
+                return CONNECTION_IS_INVALID;
+            }
+            finally
+            { 
+                ResultSetUtils.attemptClose( rs ); 
+                StatementUtils.attemptClose( stmt );
+
+//              if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX &&  logger.isLoggable( MLevel.FINER ) )
+//              logger.finer("Exiting DefaultConnectionTester.activeCheckConnection(Connection c, String query). [query=" + query + "]");
+            }
+        }
+    }
+
+    public int statusOnException(Connection c, Throwable t, String query, Throwable[] rootCauseOutParamHolder)
+    {
+//      if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ) )
+//      logger.finer("Entering DefaultConnectionTester.statusOnException(Connection c, Throwable t, String query) " + queryInfo(query));
+
+        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ) )
+            logger.log(MLevel.FINER, "Testing a Connection in response to an Exception:", t);
+
+        try
+        {
+            if (t instanceof SQLException)
+            { 
+                String state = ((SQLException) t).getSQLState();
+                if ( INVALID_DB_STATES.contains( state ) )
+                {
+                    if (logger.isLoggable(MLevel.WARNING))
+                        logger.log(MLevel.WARNING,
+                                        "SQL State '" + state + 
+                                        "' of Exception tested by statusOnException() implies that the database is invalid, " + 
+                                        "and the pool should refill itself with fresh Connections.", t);
+                    return DATABASE_IS_INVALID;
+                }
+                else
+                    return activeCheckConnection(c, query, rootCauseOutParamHolder);
+            }
+            else //something is broke
+            {
+                if ( logger.isLoggable( MLevel.FINE ) )
+                    logger.log( MLevel.FINE, "Connection test failed because test-provoking Throwable is an unexpected, non-SQLException.", t);
+                if (rootCauseOutParamHolder != null)
+                    rootCauseOutParamHolder[0] = t;
+                return CONNECTION_IS_INVALID; 
+            }
+        }
+        catch (Exception e)
+        {
+            if ( Debug.DEBUG && logger.isLoggable( MLevel.FINE ))
+                logger.log( MLevel.FINE, "Connection " + c + " failed Connection test with an Exception!", e );
+
+            if (rootCauseOutParamHolder != null)
+                rootCauseOutParamHolder[0] = e;
+
+            return CONNECTION_IS_INVALID;
+        }
+        finally
+        {
+//          if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+//          {
+//          if ( logger.isLoggable( MLevel.FINER ) )
+//          logger.finer("Exiting DefaultConnectionTester.statusOnException(Connection c, Throwable t, String query) " + queryInfo(query)); 
+//          }
+        }
+    }
+
+    private static String queryInfo(String query)
+    { return (query == null ? "[using default system-table query]" : "[query=" + query + "]"); }
+
+    private int activeCheckConnectionNoQuery(Connection c,  Throwable[] rootCauseOutParamHolder)
+    {
+//      if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ) )
+//      logger.finer("Entering DefaultConnectionTester.activeCheckConnection(Connection c). [using default system-table query]");
+
+        ResultSet rs = null;
+        try
+        { 
+            rs = c.getMetaData().getTables( null, 
+                            null, 
+                            "PROBABLYNOT", 
+                            new String[] {"TABLE"} );
+            return CONNECTION_IS_OKAY;
+        }
+        catch (SQLException e)
+        { 
+            if ( Debug.DEBUG && logger.isLoggable( MLevel.FINE ))
+                logger.log( MLevel.FINE, "Connection " + c + " failed default system-table Connection test with an Exception!", e );
+
+            if (rootCauseOutParamHolder != null)
+                rootCauseOutParamHolder[0] = e;
+
+            String state = e.getSQLState();
+            if ( INVALID_DB_STATES.contains( state ) )
+            {
+                if (logger.isLoggable(MLevel.WARNING))
+                    logger.log(MLevel.WARNING,
+                                    "SQL State '" + state + 
+                                    "' of Exception which occurred during a Connection test (fallback DatabaseMetaData test) implies that the database is invalid, " + 
+                                    "and the pool should refill itself with fresh Connections.", e);
+                return DATABASE_IS_INVALID;
+            }
+            else
+                return CONNECTION_IS_INVALID; 
+        }
+        catch (Exception e)
+        {
+            if ( Debug.DEBUG && logger.isLoggable( MLevel.FINE ))
+                logger.log( MLevel.FINE, "Connection " + c + " failed default system-table Connection test with an Exception!", e );
+
+            if (rootCauseOutParamHolder != null)
+                rootCauseOutParamHolder[0] = e;
+
+            return CONNECTION_IS_INVALID;
+        }
+        finally
+        { 
+            ResultSetUtils.attemptClose( rs ); 
+//          if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ) )
+//          logger.finer("Exiting DefaultConnectionTester.activeCheckConnection(Connection c). [using default system-table query]");
+        }
+    }
+
+
+    public boolean equals( Object o )
+    { return ( o != null && o.getClass() == DefaultConnectionTester.class ); }
+
+    public int hashCode()
+    { return HASH_CODE; }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/impl/IdentityTokenResolvable.java b/src/classes/com/mchange/v2/c3p0/impl/IdentityTokenResolvable.java
new file mode 100644
index 0000000..f7e2c5a
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/IdentityTokenResolvable.java
@@ -0,0 +1,60 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import com.mchange.v2.c3p0.*;
+import java.io.ObjectStreamException;
+
+/**
+ * This is a convenient base class for all classes
+ * that wish to establish an initial identity which
+ * will be the basis of a one-per vm identity: i.e.
+ * in any vm there should only ever be a single object
+ * with a given identity token (except transiently during
+ * canonicalization)
+ *
+ * It would be convenient to put the getter/setter methods
+ * for the identity token here, but unfortunately we have no
+ * way of setting up the for Referenceability in multiple
+ * levels of a class hierarchy. So we leave the getters/setters,
+ * and variable initialization to code-generators.
+ */
+public abstract class IdentityTokenResolvable extends AbstractIdentityTokenized
+{
+    public static Object doResolve(IdentityTokenized itd)
+    { return C3P0Registry.reregister( itd ); }
+
+    protected Object readResolve() throws ObjectStreamException
+    { 
+	//System.err.println("READ RESOLVE!!!!");
+	Object out = doResolve( this ); 
+	verifyResolve( out );
+	//System.err.println("ORIG: " + this);
+	//System.err.println("RSLV: " + out);
+	return out;
+    }
+
+    protected void verifyResolve( Object o ) throws ObjectStreamException
+    {}
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/impl/IdentityTokenized.java b/src/classes/com/mchange/v2/c3p0/impl/IdentityTokenized.java
new file mode 100644
index 0000000..a422e39
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/IdentityTokenized.java
@@ -0,0 +1,30 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+public interface IdentityTokenized
+{
+    public String getIdentityToken();
+    public void setIdentityToken(String idToken);
+}
diff --git a/src/classes/com/mchange/v2/c3p0/impl/IdentityTokenizedCoalesceChecker.java b/src/classes/com/mchange/v2/c3p0/impl/IdentityTokenizedCoalesceChecker.java
new file mode 100644
index 0000000..fa6db0a
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/IdentityTokenizedCoalesceChecker.java
@@ -0,0 +1,54 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import com.mchange.v2.coalesce.*;
+
+public final class IdentityTokenizedCoalesceChecker implements CoalesceChecker
+{
+    public static IdentityTokenizedCoalesceChecker INSTANCE = new IdentityTokenizedCoalesceChecker();
+
+    public boolean checkCoalesce( Object a, Object b )
+    {
+	IdentityTokenized aa = (IdentityTokenized) a;
+	IdentityTokenized bb = (IdentityTokenized) b;
+	
+	String ta = aa.getIdentityToken();
+	String tb = bb.getIdentityToken();
+	
+	if (ta == null || tb == null)
+	    throw new NullPointerException( "[c3p0 bug] An IdentityTokenized object has no identity token set?!?! " + (ta == null ? ta : tb) );
+	else
+	    return ta.equals(tb);
+    }
+    
+    public int coalesceHash( Object a )
+    { 
+	String t = ((IdentityTokenized) a).getIdentityToken();
+	return (t != null ? t.hashCode() : 0); 
+    }
+
+    private IdentityTokenizedCoalesceChecker()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/c3p0/impl/InternalPooledConnection.java b/src/classes/com/mchange/v2/c3p0/impl/InternalPooledConnection.java
new file mode 100644
index 0000000..bb0d6ea
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/InternalPooledConnection.java
@@ -0,0 +1,34 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import javax.sql.*;
+import com.mchange.v2.c3p0.stmt.*;
+
+interface InternalPooledConnection extends PooledConnection
+{
+    public void initStatementCache( GooGooStatementCache scache );
+    public GooGooStatementCache getStatementCache();
+    public int getConnectionStatus();
+}
diff --git a/src/classes/com/mchange/v2/c3p0/impl/NewPooledConnection.java b/src/classes/com/mchange/v2/c3p0/impl/NewPooledConnection.java
new file mode 100644
index 0000000..944a4e8
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/NewPooledConnection.java
@@ -0,0 +1,740 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v2.c3p0.stmt.*;
+import com.mchange.v2.c3p0.util.*;
+import com.mchange.v2.log.*;
+
+import java.lang.reflect.Method;
+import com.mchange.v2.lang.ObjectUtils;
+import com.mchange.v2.sql.SqlUtils;
+
+public final class NewPooledConnection extends AbstractC3P0PooledConnection{
+
+    private final static MLogger logger = MLog.getLogger( NewPooledConnection.class );
+
+    private final static SQLException NORMAL_CLOSE_PLACEHOLDER = new SQLException("This pooled Connection was explicitly close()ed by " +
+    "a client, not invalidated due to an error.");
+    
+    //MT: protected by class lock
+    static Set holdabilityBugKeys = null;
+
+    //MT: thread-safe post-constructor constants
+    final Connection             physicalConnection;
+    final ConnectionTester       connectionTester;
+    final boolean                autoCommitOnClose;
+    final boolean                forceIgnoreUnresolvedTransactions;
+    final String                 preferredTestQuery;
+    final boolean                supports_setHoldability;
+    final boolean                supports_setReadOnly;
+    final boolean                supports_setTypeMap;
+    final int                    dflt_txn_isolation;
+    final String                 dflt_catalog;
+    final int                    dflt_holdability;
+    final boolean                dflt_readOnly;
+    final Map                    dflt_typeMap;
+    final ConnectionEventSupport ces;
+
+    //MT:  protected by this' lock
+    GooGooStatementCache scache                    = null;
+    Throwable            invalidatingException     = null;
+    int                  connection_status         = ConnectionTester.CONNECTION_IS_OKAY;
+    Set                  uncachedActiveStatements  = new HashSet(); //cached statements are managed by the cache
+    Map                  resultSetsForStatements   = new HashMap(); //for both cached and uncached statements
+    Set                  metaDataResultSets        = new HashSet();
+    Set                  rawConnectionResultSets   = null;          //very rarely used, so we lazy initialize...
+    boolean              connection_error_signaled = false;
+
+    //MT: thread-safe, volatile
+    volatile NewProxyConnection exposedProxy = null;
+    volatile boolean isolation_lvl_nondefault = false; 
+    volatile boolean catalog_nondefault       = false; 
+    volatile boolean holdability_nondefault   = false; 
+    volatile boolean readOnly_nondefault      = false; 
+    volatile boolean typeMap_nondefault       = false; 
+
+    // public API
+    public NewPooledConnection(Connection con, 
+                    ConnectionTester connectionTester,
+                    boolean autoCommitOnClose, 
+                    boolean forceIgnoreUnresolvedTransactions,
+                    String  preferredTestQuery,
+                    ConnectionCustomizer cc,
+                    String pdsIdt) throws SQLException
+                    { 
+        try
+        {
+            if (cc != null)
+                cc.onAcquire( con, pdsIdt );
+        }
+        catch (Exception e)
+        { throw SqlUtils.toSQLException(e); }
+
+        this.physicalConnection                = con; 
+        this.connectionTester                  = connectionTester;
+        this.autoCommitOnClose                 = autoCommitOnClose;
+        this.forceIgnoreUnresolvedTransactions = forceIgnoreUnresolvedTransactions;
+        this.preferredTestQuery                = preferredTestQuery;
+        this.supports_setHoldability           = C3P0ImplUtils.supportsMethod(con, "setHoldability", new Class[]{ int.class });
+        this.supports_setReadOnly              = C3P0ImplUtils.supportsMethod(con, "setReadOnly", new Class[]{ boolean.class });
+        this.supports_setTypeMap               = C3P0ImplUtils.supportsMethod(con, "setTypeMap", new Class[]{ Map.class });
+        this.dflt_txn_isolation                = con.getTransactionIsolation();
+        this.dflt_catalog                      = con.getCatalog();
+        this.dflt_holdability                  = (supports_setHoldability ? carefulCheckHoldability(con) : ResultSet.CLOSE_CURSORS_AT_COMMIT);
+        this.dflt_readOnly                     = (supports_setReadOnly ? carefulCheckReadOnly(con) : false);
+        this.dflt_typeMap                      = (supports_setTypeMap && (carefulCheckTypeMap(con) == null) ? null : Collections.EMPTY_MAP);
+        this.ces                               = new ConnectionEventSupport(this);
+                    }
+
+    private static int carefulCheckHoldability(Connection con)
+    {
+        try { return con.getHoldability(); }
+        catch (Exception e)
+        {
+            if (false)
+            {
+                if (logger.isLoggable(MLevel.FINER))
+                    logger.log(MLevel.FINER, con + " threw an Exception when we tried to check its default " +
+                                    "holdability. This is not usually a problem! It just means the Connection " +
+                                    "doesn't support the holdability property, and c3p0 works around this.", e);
+            }
+            return ResultSet.CLOSE_CURSORS_AT_COMMIT;
+        }
+        catch (Error e) // Some DB2 drivers apparently throw an Error here, but I'm not comfortable swallowing Errors
+        {
+            synchronized (NewPooledConnection.class)
+            {
+                if (holdabilityBugKeys == null)
+                    holdabilityBugKeys = new HashSet();
+                String hbk = holdabilityBugKey(con, e);
+                if (! holdabilityBugKeys.contains(hbk) )
+                {
+                    if (logger.isLoggable(MLevel.WARNING))
+                        logger.log(MLevel.WARNING, con + " threw an Error when we tried to check its default " +
+                                        "holdability. This is probably due to a bug in your JDBC driver that c3p0 can harmlessly " +
+                                        "work around (reported for some DB2 drivers). Please verify that the error stack trace is consistent" +
+                                        "with the getHoldability() method not being properly implemented, and is not due to some deeper problem. " +
+                                        "This message will not be repeated for Connections of type " + con.getClass().getName() + " that " +
+                                        "provoke errors of type " + e.getClass().getName() + " when getHoldability() is called.", e);
+                    holdabilityBugKeys.add(hbk);
+                }
+            }
+            return ResultSet.CLOSE_CURSORS_AT_COMMIT;
+        }
+    }
+        
+    private static String holdabilityBugKey(Connection con, Error err)
+    { return con.getClass().getName() + '|' + err.getClass().getName(); }
+
+    private static boolean carefulCheckReadOnly(Connection con)
+    {
+        try { return con.isReadOnly(); }
+        catch (Exception e)
+        {
+            if (false)
+            {
+                if (logger.isLoggable(MLevel.FINER))
+                    logger.log(MLevel.FINER, con + " threw an Exception when we tried to check its default " +
+                                    "read only state. This is not usually a problem! It just means the Connection " +
+                                    "doesn't support the readOnly property, and c3p0 works around this.", e);
+            }
+            return false;
+        }
+    }
+
+    private static Map carefulCheckTypeMap(Connection con)
+    {
+        try { return con.getTypeMap(); }
+        catch (Exception e)
+        {
+            if (false)
+            {
+                if (logger.isLoggable(MLevel.FINER))
+                    logger.log(MLevel.FINER, con + " threw an Exception when we tried to check its default " +
+                                    "type map. This is not usually a problem! It just means the Connection " +
+                                    "doesn't support the typeMap property, and c3p0 works around this.", e);
+            }
+            return null;
+        }
+    }
+
+    public synchronized Connection getConnection() throws SQLException
+    {
+        try
+        {
+            //throw new SQLException("NOT IMPLEMENTED");
+            if ( exposedProxy == null )
+            {
+                exposedProxy = new NewProxyConnection( physicalConnection, this );
+            }
+            else
+            {
+//              System.err.println("c3p0 -- Uh oh... getConnection() was called on a PooledConnection when " +
+//              "it had already provided a client with a Connection that has not yet been " +
+//              "closed. This probably indicates a bug in the connection pool!!!");
+
+                if ( logger.isLoggable( MLevel.WARNING ) )
+                    logger.warning("c3p0 -- Uh oh... getConnection() was called on a PooledConnection when " +
+                                    "it had already provided a client with a Connection that has not yet been " +
+                    "closed. This probably indicates a bug in the connection pool!!!");
+
+            }
+            return exposedProxy;
+        }
+        catch ( Exception e )
+        {
+            SQLException sqle = handleThrowable( e );
+            throw sqle;
+        }
+    }
+
+    public synchronized int getConnectionStatus()
+    { return connection_status; }
+
+    public synchronized void closeAll() throws SQLException
+    { 
+        try
+        {
+            closeAllCachedStatements(); 
+        }
+        catch ( Exception e )
+        {
+            SQLException sqle = handleThrowable( e );
+            throw sqle;
+        }
+    }
+
+    public synchronized void close() throws SQLException
+    { close( null ); }
+
+    public void addConnectionEventListener(ConnectionEventListener cel)
+    { ces.addConnectionEventListener( cel );  }
+
+    public void removeConnectionEventListener(ConnectionEventListener cel)
+    { ces.removeConnectionEventListener( cel );  }
+
+    // api for C3P0PooledConnectionPool
+    public synchronized void initStatementCache( GooGooStatementCache scache )
+    { this.scache = scache; }
+
+    public synchronized GooGooStatementCache getStatementCache()
+    { return scache; }
+
+    //api for NewProxyConnections
+    void markNewTxnIsolation( int lvl ) //intentionally unsync'd -- isolation_lvl_nondefault is marked volatile
+    { 
+        this.isolation_lvl_nondefault = (lvl != dflt_txn_isolation); 
+        //System.err.println("isolation_lvl_nondefault: " + isolation_lvl_nondefault);
+    }
+
+    void markNewCatalog( String catalog ) //intentionally unsync'd -- catalog_nondefault is marked volatile
+    { 
+        this.catalog_nondefault = ObjectUtils.eqOrBothNull(catalog, dflt_catalog); 
+    }
+
+    void markNewHoldability( int holdability ) //intentionally unsync'd -- holdability_nondefault is marked volatile
+    { 
+        this.holdability_nondefault = (holdability != dflt_holdability); 
+    }
+
+    void markNewReadOnly( boolean readOnly ) //intentionally unsync'd -- readOnly_nondefault is marked volatile
+    { 
+        this.readOnly_nondefault = (readOnly != dflt_readOnly); 
+    }
+
+    void markNewTypeMap( Map typeMap ) //intentionally unsync'd -- typeMap_nondefault is marked volatile
+    { 
+        this.typeMap_nondefault = (typeMap != dflt_typeMap);
+    }
+
+    synchronized Object checkoutStatement( Method stmtProducingMethod, Object[] args ) throws SQLException
+    { return scache.checkoutStatement( physicalConnection, stmtProducingMethod, args ); }
+
+    synchronized void checkinStatement( Statement stmt ) throws SQLException
+    { 
+        cleanupStatementResultSets( stmt );
+        scache.checkinStatement( stmt );
+    }
+
+    synchronized void markActiveUncachedStatement( Statement stmt )
+    { uncachedActiveStatements.add( stmt );  }
+
+    synchronized void markInactiveUncachedStatement( Statement stmt )
+    {
+        cleanupStatementResultSets( stmt );
+        uncachedActiveStatements.remove( stmt );  
+    }
+
+    synchronized void markActiveResultSetForStatement( Statement stmt, ResultSet rs )
+    {
+        Set rss = resultSets( stmt, true );
+        rss.add( rs );
+    }
+
+    synchronized void markInactiveResultSetForStatement( Statement stmt, ResultSet rs )
+    { 
+        Set rss = resultSets( stmt, false );
+        if (rss == null)
+        {
+            if (logger.isLoggable( MLevel.FINE ))
+                logger.fine( "ResultSet " + rs + " was apparently closed after the Statement that created it had already been closed." );
+        }
+        else if ( ! rss.remove( rs ) )
+            throw new InternalError("Marking a ResultSet inactive that we did not know was opened!");
+    }
+
+    synchronized void markActiveRawConnectionResultSet( ResultSet rs )
+    {
+        if (rawConnectionResultSets == null)
+            rawConnectionResultSets = new HashSet();
+        rawConnectionResultSets.add( rs );
+    }
+
+    synchronized void markInactiveRawConnectionResultSet( ResultSet rs )
+    { 
+        if ( ! rawConnectionResultSets.remove( rs ) )
+            throw new InternalError("Marking a raw Connection ResultSet inactive that we did not know was opened!");
+    }
+
+    synchronized void markActiveMetaDataResultSet( ResultSet rs )
+    { metaDataResultSets.add( rs ); }
+
+    synchronized void markInactiveMetaDataResultSet( ResultSet rs )
+    { metaDataResultSets.remove( rs ); }
+
+    // internal synchronization to avoid sync'ed event multicasts
+    void markClosedProxyConnection( NewProxyConnection npc, boolean txn_known_resolved ) 
+    {
+        SQLException trouble = null;
+        try
+        {
+            synchronized( this )
+            {
+                try
+                {
+                    if (npc != exposedProxy)
+                        throw new InternalError("C3P0 Error: An exposed proxy asked a PooledConnection that was not its parents to clean up its resources!");
+
+                    List closeExceptions = new LinkedList();
+                    cleanupResultSets( closeExceptions );
+                    cleanupUncachedStatements( closeExceptions );
+                    checkinAllCachedStatements( closeExceptions );
+                    if ( closeExceptions.size() > 0 )
+                    {
+//                      System.err.println("[c3p0] The following Exceptions occurred while trying to clean up a Connection's stranded resources:");
+                        if ( logger.isLoggable( MLevel.INFO ) )
+                            logger.info("[c3p0] The following Exceptions occurred while trying to clean up a Connection's stranded resources:");
+                        for ( Iterator ii = closeExceptions.iterator(); ii.hasNext(); )
+                        {
+                            Throwable t = (Throwable) ii.next();
+//                          System.err.print("[c3p0 -- conection resource close Exception]: ");
+//                          t.printStackTrace();
+                            if ( logger.isLoggable( MLevel.INFO ) )
+                                logger.log( MLevel.INFO, "[c3p0 -- conection resource close Exception]", t );
+                        }
+                    }
+                    reset( txn_known_resolved );
+                }
+                catch (SQLException e) //Connection failed to reset!
+                {
+                    //e.printStackTrace();
+                    if (Debug.DEBUG && logger.isLoggable( MLevel.FINE ))
+                        logger.log(MLevel.FINE, "An exception occurred while reseting a closed Connection. Invalidating Connection.", e);
+
+                    updateConnectionStatus( ConnectionTester.CONNECTION_IS_INVALID );
+                }
+            }
+        }
+        finally
+        {
+            if (trouble != null)
+                fireConnectionErrorOccurred( trouble ); //should not be invoked from a sync'ed block
+            else
+            {
+                exposedProxy = null; //volatile
+                fireConnectionClosed(); //should not be invoked from a sync'ed block
+            }
+        }
+    }
+
+    private void reset( boolean txn_known_resolved ) throws SQLException
+    {
+        C3P0ImplUtils.resetTxnState( physicalConnection, forceIgnoreUnresolvedTransactions, autoCommitOnClose, txn_known_resolved );
+        if (isolation_lvl_nondefault)
+        {
+            physicalConnection.setTransactionIsolation( dflt_txn_isolation );
+            isolation_lvl_nondefault = false; 
+            //System.err.println("reset txn isolation: " + dflt_txn_isolation);
+        }
+        if (catalog_nondefault)
+        {
+            physicalConnection.setCatalog( dflt_catalog );
+            catalog_nondefault = false; 
+        }
+        if (holdability_nondefault) //this cannot go to true if holdability is not supported, so we don't have to check.
+        {
+            physicalConnection.setHoldability( dflt_holdability );
+            holdability_nondefault = false; 
+        }
+        if (readOnly_nondefault)
+        {
+            physicalConnection.setReadOnly( dflt_readOnly );
+            readOnly_nondefault = false; 
+        }
+        if (typeMap_nondefault)
+        {
+            physicalConnection.setTypeMap( dflt_typeMap );
+            typeMap_nondefault = false;
+        }
+    }
+
+    synchronized boolean isStatementCaching()
+    { return scache != null; }
+
+    //synchrnized internally to avoid holding locks during event multicast
+    SQLException handleThrowable( Throwable t )
+    {
+        boolean fire_cxn_error = false;
+        SQLException sqle = null;
+        try
+        {
+            synchronized (this)
+            {
+                if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ))
+                    logger.log( MLevel.FINER, this + " handling a throwable.", t );
+
+                sqle = SqlUtils.toSQLException( t );
+                //logger.warning("handle throwable ct: " + connectionTester);
+
+                int status;
+                if (connectionTester instanceof FullQueryConnectionTester)
+                    status = ((FullQueryConnectionTester) connectionTester).statusOnException( physicalConnection, sqle, preferredTestQuery );
+                else
+                    status = connectionTester.statusOnException( physicalConnection, sqle );
+
+                updateConnectionStatus( status ); 
+                if (status != ConnectionTester.CONNECTION_IS_OKAY)
+                {
+                    if (Debug.DEBUG)
+                    {
+//                      System.err.print(this + " invalidated by Exception: ");
+//                      t.printStackTrace();
+                        if ( logger.isLoggable( MLevel.FINE ) )
+                            logger.log(MLevel.FINE, this + " invalidated by Exception.", t);
+                    }
+
+                    /*
+                  ------
+                  A users have complained that SQLExceptions ought not close their Connections underneath
+                  them under any circumstance. Signalling the Connection error after updating the Connection
+                  status should be sufficient from the pool's perspective, because the PooledConnection
+                  will be marked broken by the pool and will be destroyed on checkin. I think actually
+                  close()ing the Connection when it appears to be broken rather than waiting for users
+                  to close() it themselves is overly aggressive, so I'm commenting the old behavior out.
+                  The only potential downside to this approach is that users who do not close() in a finally
+                  clause properly might see their close()es skipped by exceptions that previously would
+                  have led to automatic close(). But relying on the automatic close() was never reliable
+                  (since it only ever happened when c3p0 determined a Connection to be absolutely broken),
+                  and is generally speaking a client error that c3p0 ought not be responsible for dealing
+                  with. I think it's right to leave this out. -- swaldman 2004-12-09
+                  ------
+
+                try { close( t ); }
+                catch (SQLException e)
+                    {
+                    e.printStackTrace();
+                    throw new InternalError("C3P0 Error: NewPooledConnection's private close() method should " +
+                                "suppress any Exceptions if a throwable cause is provided.");
+                    }
+                     */
+
+
+                    if (! connection_error_signaled)
+                        fire_cxn_error = true;
+                    else
+                    {
+//                      System.err.println("[c3p0] Warning: PooledConnection that has already signalled a Connection error is still in use!");
+//                      System.err.println("[c3p0] Another error has occurred [ " + t + " ] which will not be reported to listeners!");
+                        if ( logger.isLoggable( MLevel.WARNING ) )
+                        {
+                            logger.log(MLevel.WARNING, "[c3p0] A PooledConnection that has already signalled a Connection error is still in use!");
+                            logger.log(MLevel.WARNING, "[c3p0] Another error has occurred [ " + t + " ] which will not be reported to listeners!", t);
+                        }
+                    }
+                }
+            }// end sync'ed block
+        }// end try block
+        finally
+        {
+            if (fire_cxn_error)
+            {
+                fireConnectionErrorOccurred( sqle ); //should not be invoked from a sync'ed block
+                connection_error_signaled = true;
+            }
+        }
+        return sqle;
+    }
+
+//  private methods
+
+//  should NOT be called from sync'ed method
+    private void fireConnectionClosed()
+    {
+        assert (! Thread.holdsLock(this));
+        ces.fireConnectionClosed(); 
+    }
+
+//  should NOT be called from sync'ed method
+    private void fireConnectionErrorOccurred(SQLException error)
+    { 
+        assert (! Thread.holdsLock(this));
+        ces.fireConnectionErrorOccurred( error ); 
+    }
+
+//  methods below must be called from sync'ed methods
+
+    /*
+     *  If a throwable cause is provided, the PooledConnection is known to be broken (cause is an invalidating exception)
+     *  and this method will not throw any exceptions, even if some resource closes fail.
+     *
+     *  If cause is null, then we think the PooledConnection is healthy, and we will report (throw) an exception
+     *  if resources unexpectedlay fail to close.
+     */
+    private void close( Throwable cause ) throws SQLException
+    {
+        if ( this.invalidatingException == null )
+        {
+            List closeExceptions = new LinkedList();
+
+            // cleanup ResultSets
+            cleanupResultSets( closeExceptions );
+
+            // cleanup uncached Statements
+            cleanupUncachedStatements( closeExceptions );
+
+            // cleanup cached Statements
+            try
+            { closeAllCachedStatements(); }
+            catch ( SQLException e )
+            { closeExceptions.add(e); }
+
+            // cleanup physicalConnection
+            try
+            { physicalConnection.close(); }
+            catch ( SQLException e )
+            {
+                if (logger.isLoggable( MLevel.FINER ))
+                    logger.log( MLevel.FINER, "Failed to close physical Connection: " + physicalConnection, e );
+
+                closeExceptions.add(e); 
+            }
+
+            // update our state to bad status and closed, and log any exceptions
+            if ( connection_status == ConnectionTester.CONNECTION_IS_OKAY )
+                connection_status = ConnectionTester.CONNECTION_IS_INVALID;
+            if ( cause == null )
+            {
+                this.invalidatingException = NORMAL_CLOSE_PLACEHOLDER;
+
+                if ( logger.isLoggable( MLevel.FINEST ) )
+                    logger.log( MLevel.FINEST, this + " closed by a client.", new Exception("DEBUG -- CLOSE BY CLIENT STACK TRACE") );
+
+                logCloseExceptions( null, closeExceptions );
+
+                if (closeExceptions.size() > 0)
+                    throw new SQLException("Some resources failed to close properly while closing " + this);
+            }
+            else
+            {
+                this.invalidatingException = cause;
+                if (Debug.TRACE >= Debug.TRACE_MED)
+                    logCloseExceptions( cause, closeExceptions );
+                else
+                    logCloseExceptions( cause, null );
+            }
+        }
+    }
+
+    private void cleanupResultSets( List closeExceptions )
+    {
+        cleanupAllStatementResultSets( closeExceptions );
+        cleanupUnclosedResultSetsSet( metaDataResultSets, closeExceptions );
+        if ( rawConnectionResultSets != null )
+            cleanupUnclosedResultSetsSet( rawConnectionResultSets, closeExceptions );
+    }
+
+    private void cleanupUnclosedResultSetsSet( Set rsSet, List closeExceptions )
+    {
+        for ( Iterator ii = rsSet.iterator(); ii.hasNext(); )
+        {
+            ResultSet rs = (ResultSet) ii.next();
+            try
+            { rs.close(); }
+            catch ( SQLException e )
+            { closeExceptions.add(e); }
+
+            ii.remove();
+        }
+    }
+
+    private void cleanupStatementResultSets( Statement stmt )
+    {
+        Set rss = resultSets( stmt, false );
+        if ( rss != null )
+        {
+            for ( Iterator ii = rss.iterator(); ii.hasNext(); )
+            {
+                try
+                { ((ResultSet) ii.next()).close(); }
+                catch ( Exception e )
+                {
+//                  System.err.print("ResultSet close() failed: ");
+//                  e.printStackTrace();
+                    if ( logger.isLoggable( MLevel.INFO ) )
+                        logger.log(MLevel.INFO, "ResultSet close() failed.", e);
+                }
+            }
+        }
+        resultSetsForStatements.remove( stmt );
+    }
+
+    private void cleanupAllStatementResultSets( List closeExceptions )
+    {
+        for ( Iterator ii = resultSetsForStatements.keySet().iterator(); ii.hasNext(); )
+        {
+            Object stmt = ii.next();
+            Set rss = (Set) resultSetsForStatements.get( stmt );
+            for (Iterator jj = rss.iterator(); jj.hasNext(); )
+            {
+                ResultSet rs = (ResultSet) jj.next();
+                try
+                { rs.close(); }
+                catch ( SQLException e )
+                { closeExceptions.add(e); }
+            }
+        }
+        resultSetsForStatements.clear();
+    }
+
+    private void cleanupUncachedStatements( List closeExceptions )
+    {
+        for ( Iterator ii = uncachedActiveStatements.iterator(); ii.hasNext(); )
+        {
+            Statement stmt = (Statement) ii.next();
+            try
+            { stmt.close(); }
+            catch ( SQLException e )
+            { closeExceptions.add(e); }
+
+            ii.remove();
+        }
+    }
+
+    private void checkinAllCachedStatements( List closeExceptions )
+    {
+        try
+        {
+            if (scache != null)
+                scache.checkinAll( physicalConnection );
+        }
+        catch ( SQLException e )
+        { closeExceptions.add(e); }
+    }
+
+    private void closeAllCachedStatements() throws SQLException
+    {
+        if (scache != null)
+            scache.closeAll( physicalConnection );
+    }
+
+    private void updateConnectionStatus(int status)
+    {
+        switch ( this.connection_status )
+        {
+        case ConnectionTester.DATABASE_IS_INVALID:
+            //can't get worse than this, do nothing.
+            break;
+        case ConnectionTester.CONNECTION_IS_INVALID:
+            if (status == ConnectionTester.DATABASE_IS_INVALID)
+                this.connection_status = status;
+            break;
+        case ConnectionTester.CONNECTION_IS_OKAY:
+            if (status != ConnectionTester.CONNECTION_IS_OKAY)
+                this.connection_status = status;
+            break;
+        default:
+            throw new InternalError(this + " -- Illegal Connection Status: " + this.connection_status);
+        }
+    }
+
+    private Set resultSets( Statement stmt, boolean create )
+    { 
+        Set out = (Set) resultSetsForStatements.get( stmt ); 
+        if ( out == null && create )
+        {
+            out = new HashSet();
+            resultSetsForStatements.put( stmt, out );
+        }
+        return out;
+    }
+
+//  used by C3P0PooledConnectionPool
+    Connection getPhysicalConnection()
+    { return physicalConnection; }
+
+//  static utility functions
+    private static void logCloseExceptions( Throwable cause, Collection exceptions )
+    {
+        if ( logger.isLoggable( MLevel.INFO ) )
+        {
+            if (cause != null)
+            {
+                // 		System.err.println("[c3p0] A PooledConnection died due to the following error!");
+                // 		cause.printStackTrace();
+                logger.log(MLevel.INFO, "[c3p0] A PooledConnection died due to the following error!", cause);
+            }
+            if ( exceptions != null && exceptions.size() > 0)
+            {
+                if ( cause == null )
+                    logger.info("[c3p0] Exceptions occurred while trying to close a PooledConnection's resources normally.");
+                //System.err.println("[c3p0] The following Exceptions occurred while trying to close a PooledConnection's resources normally.");
+                else
+                    logger.info("[c3p0] Exceptions occurred while trying to close a Broken PooledConnection.");
+                //System.err.println("[c3p0] The following Exceptions occurred while trying to close a broken PooledConnection.");
+                for ( Iterator ii = exceptions.iterator(); ii.hasNext(); )
+                {
+                    Throwable t = (Throwable) ii.next();
+                    // 			System.err.print("[c3p0 -- close Exception]: ");
+                    // 			t.printStackTrace();
+                    logger.log(MLevel.INFO, "[c3p0] NewPooledConnection close Exception.", t);
+                }
+            }
+        }
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/impl/NullStatementSetManagedResultSet.java b/src/classes/com/mchange/v2/c3p0/impl/NullStatementSetManagedResultSet.java
new file mode 100644
index 0000000..a0998c6
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/NullStatementSetManagedResultSet.java
@@ -0,0 +1,47 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+/*
+ * Created on Apr 6, 2004
+ *
+ * To change the template for this generated file go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+package com.mchange.v2.c3p0.impl;
+
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.Set;
+
+
+final class NullStatementSetManagedResultSet extends SetManagedResultSet
+{
+NullStatementSetManagedResultSet(Set activeResultSets)
+{ super( activeResultSets ); }
+
+NullStatementSetManagedResultSet(ResultSet inner, Set activeResultSets)
+{ super( inner, activeResultSets); }
+
+public Statement getStatement()
+{ return null; }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/impl/SetManagedDatabaseMetaData.java b/src/classes/com/mchange/v2/c3p0/impl/SetManagedDatabaseMetaData.java
new file mode 100644
index 0000000..83d5c80
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/SetManagedDatabaseMetaData.java
@@ -0,0 +1,136 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.sql.*;
+import java.util.Set;
+import com.mchange.v2.sql.filter.FilterDatabaseMetaData;
+
+final class SetManagedDatabaseMetaData extends FilterDatabaseMetaData
+{
+    Set activeResultSets;
+    Connection returnableProxy;
+
+    SetManagedDatabaseMetaData( DatabaseMetaData inner, Set activeResultSets, Connection returnableProxy )
+    {
+		super( inner );
+		this.activeResultSets = activeResultSets;
+		this.returnableProxy = returnableProxy;
+    }
+
+    public Connection getConnection() throws SQLException
+    { return returnableProxy; }
+
+    public ResultSet getProcedures(String a, String b, String c) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getProcedures(a, b, c), activeResultSets );
+    }
+
+    public ResultSet getProcedureColumns(String a, String b, String c, String d) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getProcedureColumns(a, b, c, d), activeResultSets );
+    }
+
+    public ResultSet getTables(String a, String b, String c, String[] d) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getTables(a, b, c, d), activeResultSets );
+    }
+
+    public ResultSet getSchemas() throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getSchemas(), activeResultSets );
+    }
+
+    public ResultSet getCatalogs() throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getCatalogs(), activeResultSets );
+    }
+
+    public ResultSet getTableTypes() throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getTableTypes(), activeResultSets );
+    }
+
+    public ResultSet getColumns(String a, String b, String c, String d) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getColumns(a, b, c, d), activeResultSets );
+    }
+
+    public ResultSet getColumnPrivileges(String a, String b, String c, String d) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getColumnPrivileges(a, b, c, d), activeResultSets );
+    }
+
+    public ResultSet getTablePrivileges(String a, String b, String c) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getTablePrivileges(a, b, c), activeResultSets );
+    }
+
+    public ResultSet getBestRowIdentifier(String a, String b, String c, int d, boolean e) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getBestRowIdentifier(a, b, c, d, e), activeResultSets );
+    }
+
+    public ResultSet getVersionColumns(String a, String b, String c) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getVersionColumns(a, b, c), activeResultSets );
+    }
+
+    public ResultSet getPrimaryKeys(String a, String b, String c) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getPrimaryKeys(a, b, c), activeResultSets );
+    }
+
+    public ResultSet getImportedKeys(String a, String b, String c) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getImportedKeys(a, b, c), activeResultSets );
+    }
+
+    public ResultSet getExportedKeys(String a, String b, String c) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getExportedKeys(a, b, c), activeResultSets );
+    }
+
+    public ResultSet getCrossReference(String a, String b, String c, String d, String e, String f) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getCrossReference(a, b, c, d, e, f), activeResultSets );
+    }
+
+    public ResultSet getTypeInfo() throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getTypeInfo(), activeResultSets );
+    }
+
+    public ResultSet getIndexInfo(String a, String b, String c, boolean d, boolean e) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getIndexInfo(a, b, c, d, e), activeResultSets );
+    }
+
+    public ResultSet getUDTs(String a, String b, String c, int[] d) throws SQLException
+    {
+        return new NullStatementSetManagedResultSet( inner.getUDTs(a, b, c, d), activeResultSets );
+    }
+}
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/impl/SetManagedResultSet.java b/src/classes/com/mchange/v2/c3p0/impl/SetManagedResultSet.java
new file mode 100644
index 0000000..e05b463
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/SetManagedResultSet.java
@@ -0,0 +1,60 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.sql.*;
+import java.util.Set;
+import com.mchange.v2.sql.filter.FilterResultSet;
+
+abstract class SetManagedResultSet extends FilterResultSet
+{
+    Set activeResultSets;
+
+    SetManagedResultSet(Set activeResultSets)
+    {
+ 	this.activeResultSets = activeResultSets; 
+    }
+
+    SetManagedResultSet(ResultSet inner, Set activeResultSets)
+    { 
+	super( inner );
+ 	this.activeResultSets = activeResultSets; 
+    }
+
+    public synchronized void setInner(ResultSet inner)
+    {
+	this.inner = inner;
+	activeResultSets.add( inner );
+    }
+    
+    public synchronized void close() throws SQLException
+    { 
+	if ( inner != null )
+	    {
+		inner.close();
+		activeResultSets.remove( inner ); 
+		inner = null;
+	    }
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/impl/SnatchFromSetResultSet.java b/src/classes/com/mchange/v2/c3p0/impl/SnatchFromSetResultSet.java
new file mode 100644
index 0000000..1eaf123
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/impl/SnatchFromSetResultSet.java
@@ -0,0 +1,49 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.impl;
+
+import java.sql.*;
+import java.util.Set;
+import com.mchange.v2.sql.filter.FilterResultSet;
+
+final class SnatchFromSetResultSet extends FilterResultSet
+{
+    Set activeResultSets;
+
+    SnatchFromSetResultSet(Set activeResultSets)
+    { this.activeResultSets = activeResultSets; }
+
+    public synchronized void setInner(ResultSet inner)
+    {
+	this.inner = inner;
+	activeResultSets.add( inner );
+    }
+    
+    public synchronized void close() throws SQLException
+    { 
+	inner.close();
+	activeResultSets.remove( inner ); 
+	inner = null;
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/jboss/C3P0PooledDataSource.java b/src/classes/com/mchange/v2/c3p0/jboss/C3P0PooledDataSource.java
new file mode 100644
index 0000000..bc4303d
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/jboss/C3P0PooledDataSource.java
@@ -0,0 +1,500 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.jboss;
+
+import com.mchange.v2.c3p0.*;
+import com.mchange.v2.log.*;
+import java.beans.PropertyVetoException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.io.PrintWriter;
+import java.util.Properties;
+import javax.sql.DataSource;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.Context;
+import javax.naming.NameAlreadyBoundException;
+import javax.naming.NamingException;
+
+public class C3P0PooledDataSource implements C3P0PooledDataSourceMBean
+{
+    private final static MLogger logger = MLog.getLogger( C3P0PooledDataSource.class );
+
+    String jndiName;
+
+    ComboPooledDataSource combods = new ComboPooledDataSource();
+
+    private void rebind() throws NamingException
+    { rebind(null); }
+
+    private void rebind(String unbindName) throws NamingException
+    {
+	InitialContext ictx = new InitialContext();
+	if (unbindName != null)
+	    ictx.unbind( unbindName );
+	
+	if (jndiName != null)
+	{
+	    // Thanks to David D. Kilzer for this code to auto-create
+	    // subcontext paths!
+	    Name name = ictx.getNameParser( jndiName ).parse( jndiName );
+	    Context ctx = ictx;
+	    for (int i = 0, max = name.size() - 1; i < max; i++)
+	    {
+		try
+		{ ctx = ctx.createSubcontext( name.get( i ) ); }
+		catch (NameAlreadyBoundException ignore)
+		{ ctx = (Context) ctx.lookup( name.get( i ) ); }
+	    }
+
+ 	    ictx.rebind( jndiName, combods );
+	}
+
+
+    }
+
+    // Jndi Setup Names
+    public void setJndiName(String jndiName) throws NamingException
+    {
+	String unbindName = this.jndiName;
+	this.jndiName = jndiName; 
+	rebind( unbindName );
+    }
+
+    public String getJndiName()
+    { return jndiName; }
+
+    // DriverManagerDataSourceProperties  (count: 4)
+    public String getDescription()
+    { return combods.getDescription(); }
+	
+    public void setDescription( String description ) throws NamingException
+    { 
+	combods.setDescription( description ); 
+	rebind();
+    }
+	
+    public String getDriverClass()
+    { return combods.getDriverClass(); }
+	
+    public void setDriverClass( String driverClass ) throws PropertyVetoException, NamingException
+    { 
+	combods.setDriverClass( driverClass ); 
+	rebind();
+    }
+	
+    public String getJdbcUrl()
+    { return combods.getJdbcUrl(); }
+	
+    public void setJdbcUrl( String jdbcUrl ) throws NamingException
+    { 
+	combods.setJdbcUrl( jdbcUrl ); 
+	rebind();
+    }
+	
+    // DriverManagerDataSource "virtual properties" based on properties
+    public String getUser()
+    { return combods.getUser(); }
+	
+    public void setUser( String user ) throws NamingException
+    { 
+	combods.setUser( user ); 
+	rebind();
+    }
+	
+    public String getPassword()
+    { return combods.getPassword(); }
+	
+    public void setPassword( String password ) throws NamingException
+    { 
+	combods.setPassword( password ); 
+	rebind();
+    }
+
+    // WrapperConnectionPoolDataSource properties (count: 21)
+    public int getCheckoutTimeout()
+    { return combods.getCheckoutTimeout(); }
+	
+    public void setCheckoutTimeout( int checkoutTimeout ) throws NamingException
+    { 
+	combods.setCheckoutTimeout( checkoutTimeout ); 
+	rebind();
+    }
+
+    public int getAcquireIncrement()
+    { return combods.getAcquireIncrement(); }
+	
+    public void setAcquireIncrement( int acquireIncrement ) throws NamingException
+    { 
+	combods.setAcquireIncrement( acquireIncrement ); 
+	rebind();
+    }
+	
+    public int getAcquireRetryAttempts()
+    { return combods.getAcquireRetryAttempts(); }
+	
+    public void setAcquireRetryAttempts( int acquireRetryAttempts ) throws NamingException
+    { 
+	combods.setAcquireRetryAttempts( acquireRetryAttempts ); 
+	rebind();
+    }
+	
+    public int getAcquireRetryDelay()
+    { return combods.getAcquireRetryDelay(); }
+	
+    public void setAcquireRetryDelay( int acquireRetryDelay ) throws NamingException
+    { 
+	combods.setAcquireRetryDelay( acquireRetryDelay ); 
+	rebind();
+    }
+	
+    public boolean isAutoCommitOnClose()
+    { return combods.isAutoCommitOnClose(); }
+
+    public void setAutoCommitOnClose( boolean autoCommitOnClose ) throws NamingException
+    { 
+	combods.setAutoCommitOnClose( autoCommitOnClose ); 
+	rebind();
+    }
+	
+    public String getConnectionTesterClassName()
+    { return combods.getConnectionTesterClassName(); }
+	
+    public void setConnectionTesterClassName( String connectionTesterClassName ) throws PropertyVetoException, NamingException
+    { 
+	combods.setConnectionTesterClassName( connectionTesterClassName ); 
+	rebind();
+    }
+	
+    public String getAutomaticTestTable()
+    { return combods.getAutomaticTestTable(); }
+	
+    public void setAutomaticTestTable( String automaticTestTable ) throws NamingException
+    { 
+	combods.setAutomaticTestTable( automaticTestTable ); 
+	rebind();
+    }
+	
+    public boolean isForceIgnoreUnresolvedTransactions()
+    { return combods.isForceIgnoreUnresolvedTransactions(); }
+	
+    public void setForceIgnoreUnresolvedTransactions( boolean forceIgnoreUnresolvedTransactions ) throws NamingException
+    { 
+	combods.setForceIgnoreUnresolvedTransactions( forceIgnoreUnresolvedTransactions ); 
+	rebind();
+    }
+	
+    public int getIdleConnectionTestPeriod()
+    { return combods.getIdleConnectionTestPeriod(); }
+	
+    public void setIdleConnectionTestPeriod( int idleConnectionTestPeriod ) throws NamingException
+    { 
+	combods.setIdleConnectionTestPeriod( idleConnectionTestPeriod ); 
+	rebind();
+    }
+    
+    public int getInitialPoolSize()
+    { return combods.getInitialPoolSize(); }
+	
+    public void setInitialPoolSize( int initialPoolSize ) throws NamingException
+    { 
+	combods.setInitialPoolSize( initialPoolSize ); 
+	rebind();
+    }
+
+    public int getMaxIdleTime()
+    { return combods.getMaxIdleTime(); }
+	
+    public void setMaxIdleTime( int maxIdleTime ) throws NamingException
+    { 
+	combods.setMaxIdleTime( maxIdleTime ); 
+	rebind();
+    }
+	
+    public int getMaxPoolSize()
+    { return combods.getMaxPoolSize(); }
+	
+    public void setMaxPoolSize( int maxPoolSize ) throws NamingException
+    { 
+	combods.setMaxPoolSize( maxPoolSize ); 
+	rebind();
+    }
+	
+    public int getMaxStatements()
+    { return combods.getMaxStatements(); }
+	
+    public void setMaxStatements( int maxStatements ) throws NamingException
+    { 
+	combods.setMaxStatements( maxStatements ); 
+	rebind();
+    }
+	
+    public int getMaxStatementsPerConnection()
+    { return combods.getMaxStatementsPerConnection(); }
+	
+    public void setMaxStatementsPerConnection( int maxStatementsPerConnection ) throws NamingException
+    { 
+	combods.setMaxStatementsPerConnection( maxStatementsPerConnection ); 
+	rebind();
+    }
+	
+    public int getMinPoolSize()
+    { return combods.getMinPoolSize(); }
+	
+    public void setMinPoolSize( int minPoolSize ) throws NamingException
+    { 
+	combods.setMinPoolSize( minPoolSize ); 
+	rebind();
+    }
+	
+    public int getPropertyCycle()
+    { return combods.getPropertyCycle(); }
+	
+    public void setPropertyCycle( int propertyCycle ) throws NamingException
+    { 
+	combods.setPropertyCycle( propertyCycle ); 
+	rebind();
+    }
+    
+    public boolean isBreakAfterAcquireFailure()
+    { return combods.isBreakAfterAcquireFailure(); }
+    
+    public void setBreakAfterAcquireFailure( boolean breakAfterAcquireFailure ) throws NamingException
+    { 
+	combods.setBreakAfterAcquireFailure( breakAfterAcquireFailure ); 
+	rebind();
+    }
+    
+    public boolean isTestConnectionOnCheckout()
+    { return combods.isTestConnectionOnCheckout(); }
+	
+    public void setTestConnectionOnCheckout( boolean testConnectionOnCheckout ) throws NamingException
+    { 
+	combods.setTestConnectionOnCheckout( testConnectionOnCheckout ); 
+	rebind();
+    }
+	
+    public boolean isTestConnectionOnCheckin()
+    { return combods.isTestConnectionOnCheckin(); }
+	
+    public void setTestConnectionOnCheckin( boolean testConnectionOnCheckin ) throws NamingException
+    { 
+	combods.setTestConnectionOnCheckin( testConnectionOnCheckin ); 
+	rebind();
+    }
+	
+    public boolean isUsesTraditionalReflectiveProxies()
+    { return combods.isUsesTraditionalReflectiveProxies(); }
+	
+    public void setUsesTraditionalReflectiveProxies( boolean usesTraditionalReflectiveProxies ) throws NamingException
+    { 
+	combods.setUsesTraditionalReflectiveProxies( usesTraditionalReflectiveProxies ); 
+	rebind();
+    }
+
+    public String getPreferredTestQuery()
+    { return combods.getPreferredTestQuery(); }
+	
+    public void setPreferredTestQuery( String preferredTestQuery ) throws NamingException
+    { 
+	combods.setPreferredTestQuery( preferredTestQuery ); 
+	rebind();
+    }
+
+    // PoolBackedDataSource properties (count: 2)
+    public String getDataSourceName()
+    { return combods.getDataSourceName(); }
+	
+    public void setDataSourceName( String name ) throws NamingException
+    { 
+	combods.setDataSourceName( name ); 
+	rebind();
+    }
+
+    public int getNumHelperThreads()
+    { return combods.getNumHelperThreads(); }
+	
+    public void setNumHelperThreads( int numHelperThreads ) throws NamingException
+    { 
+	combods.setNumHelperThreads( numHelperThreads ); 
+	rebind();
+    }
+
+    // shared properties (count: 1)
+    public String getFactoryClassLocation()
+    { return combods.getFactoryClassLocation(); }
+    
+    public void setFactoryClassLocation( String factoryClassLocation ) throws NamingException
+    { 
+	combods.setFactoryClassLocation( factoryClassLocation ); 
+	rebind();
+    }
+
+    // PooledDataSource statistics
+
+    public int getNumUserPools() throws SQLException
+    { return combods.getNumUserPools(); }
+
+    public int getNumConnectionsDefaultUser() throws SQLException
+    { return combods.getNumConnectionsDefaultUser(); }
+
+    public int getNumIdleConnectionsDefaultUser() throws SQLException
+    { return combods.getNumIdleConnectionsDefaultUser(); }
+
+    public int getNumBusyConnectionsDefaultUser() throws SQLException
+    { return combods.getNumBusyConnectionsDefaultUser(); }
+
+    public int getNumUnclosedOrphanedConnectionsDefaultUser() throws SQLException
+    { return combods.getNumUnclosedOrphanedConnectionsDefaultUser(); }
+
+    public int getNumConnections(String username, String password) throws SQLException
+    { return combods.getNumConnections(username, password); }
+
+    public int getNumIdleConnections(String username, String password) throws SQLException
+    { return combods.getNumIdleConnections(username, password); }
+
+    public int getNumBusyConnections(String username, String password) throws SQLException
+    { return combods.getNumBusyConnections(username, password); }
+
+    public int getNumUnclosedOrphanedConnections(String username, String password) throws SQLException
+    { return combods.getNumUnclosedOrphanedConnections(username, password); }
+
+    public int getNumConnectionsAllUsers() throws SQLException
+    { return combods.getNumConnectionsAllUsers(); }
+
+    public int getNumIdleConnectionsAllUsers() throws SQLException
+    { return combods.getNumIdleConnectionsAllUsers(); }
+
+    public int getNumBusyConnectionsAllUsers() throws SQLException
+    { return combods.getNumBusyConnectionsAllUsers(); }
+
+    public int getNumUnclosedOrphanedConnectionsAllUsers() throws SQLException
+    { return combods.getNumUnclosedOrphanedConnectionsAllUsers(); }
+
+    // PooledDataSource operations
+    public void softResetDefaultUser() throws SQLException
+    { combods.softResetDefaultUser(); }
+
+    public void softReset(String username, String password) throws SQLException
+    { combods.softReset(username, password); }
+
+    public void softResetAllUsers() throws SQLException
+    { combods.softResetAllUsers(); }
+
+    public void hardReset() throws SQLException
+    { combods.hardReset(); }
+
+    public void close() throws SQLException
+    { combods.close(); }
+
+    //JBoss only... (but these methods need not be called for the mbean to work)
+    public void create() throws Exception
+    { }
+
+    // the mbean works without this, but if called we start populating the pool early
+    public void start() throws Exception
+    { 
+	//System.err.println("Bound C3P0 PooledDataSource to name '" + jndiName + "'. Starting..."); 
+	logger.log(MLevel.INFO, "Bound C3P0 PooledDataSource to name ''{0}''. Starting...", jndiName); 
+	combods.getNumBusyConnectionsDefaultUser(); //just touch the datasource to start it up.
+    }
+
+
+    public void stop()
+    { }
+
+    public void destroy()
+    { 
+        try
+        {
+            combods.close();
+            logger.log(MLevel.INFO, "Destroyed C3P0 PooledDataSource with name ''{0}''.", jndiName); 
+        }
+        catch (Exception e)
+        {
+            logger.log(MLevel.INFO, "Failed to destroy C3P0 PooledDataSource.", e); 
+        }
+    }
+
+    public String getConnectionCustomizerClassName()
+    { return combods.getConnectionCustomizerClassName(); }
+
+    public float getEffectivePropertyCycle(String username, String password) throws SQLException
+    { return combods.getEffectivePropertyCycle(username, password); }
+
+    public float getEffectivePropertyCycleDefaultUser() throws SQLException
+    { return combods.getEffectivePropertyCycleDefaultUser(); }
+
+    public int getMaxAdministrativeTaskTime()
+    { return combods.getMaxAdministrativeTaskTime(); }
+
+    public int getMaxConnectionAge()
+    { return combods.getMaxConnectionAge(); }
+
+    public int getMaxIdleTimeExcessConnections()
+    { return combods.getMaxIdleTimeExcessConnections(); }
+
+    public int getUnreturnedConnectionTimeout()
+    { return combods.getUnreturnedConnectionTimeout(); }
+
+    public boolean isDebugUnreturnedConnectionStackTraces()
+    { return combods.isDebugUnreturnedConnectionStackTraces(); }
+
+    public void setConnectionCustomizerClassName(String connectionCustomizerClassName) throws NamingException
+    {
+        combods.setConnectionCustomizerClassName(connectionCustomizerClassName);
+        rebind();
+    }
+
+    public void setDebugUnreturnedConnectionStackTraces(boolean debugUnreturnedConnectionStackTraces) throws NamingException
+    {
+        combods.setDebugUnreturnedConnectionStackTraces(debugUnreturnedConnectionStackTraces);
+        rebind();
+    }
+
+    public void setMaxAdministrativeTaskTime(int maxAdministrativeTaskTime) throws NamingException
+    {
+        combods.setMaxAdministrativeTaskTime(maxAdministrativeTaskTime);
+        rebind();
+    }
+
+    public void setMaxConnectionAge(int maxConnectionAge) throws NamingException
+    {
+        combods.setMaxConnectionAge( maxConnectionAge );
+        rebind();
+    }
+
+    public void setMaxIdleTimeExcessConnections(int maxIdleTimeExcessConnections) throws NamingException
+    {
+        combods.setMaxIdleTimeExcessConnections(maxIdleTimeExcessConnections);
+        rebind();
+    }
+
+    public void setUnreturnedConnectionTimeout(int unreturnedConnectionTimeout) throws NamingException
+    {
+        combods.setUnreturnedConnectionTimeout(unreturnedConnectionTimeout);
+        rebind();
+    }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/jboss/C3P0PooledDataSourceMBean.java b/src/classes/com/mchange/v2/c3p0/jboss/C3P0PooledDataSourceMBean.java
new file mode 100644
index 0000000..236fa02
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/jboss/C3P0PooledDataSourceMBean.java
@@ -0,0 +1,184 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.jboss;
+
+import com.mchange.v2.c3p0.*;
+import java.beans.PropertyVetoException;
+import java.sql.SQLException;
+import java.util.Properties;
+import javax.naming.NamingException;
+
+public interface C3P0PooledDataSourceMBean
+{
+    // Jndi Setup 
+    public void setJndiName(String jndiName) throws NamingException;
+
+    public String getJndiName();
+
+    // DriverManagerDataSourceProperties
+    public String getDescription();
+	
+    public void setDescription( String description ) throws NamingException;
+	
+    public String getDriverClass();
+	
+    public void setDriverClass( String driverClass ) throws PropertyVetoException, NamingException;
+	
+    public String getJdbcUrl();
+	
+    public void setJdbcUrl( String jdbcUrl ) throws NamingException;
+	
+    // DriverManagerDataSource "virtual properties" based on properties
+    public String getUser();
+	
+    public void setUser( String user ) throws NamingException;
+	
+    public String getPassword();
+	
+    public void setPassword( String password ) throws NamingException;
+
+    // WrapperConnectionPoolDataSource properties 
+    public int getUnreturnedConnectionTimeout();
+    public void setUnreturnedConnectionTimeout(int unreturnedConnectionTimeout) throws NamingException;
+    
+    public boolean isDebugUnreturnedConnectionStackTraces();
+    public void setDebugUnreturnedConnectionStackTraces(boolean debugUnreturnedConnectionStackTraces) throws NamingException;
+    
+    public String getConnectionCustomizerClassName();
+    public void setConnectionCustomizerClassName( String connectionCustomizerClassName ) throws NamingException;
+
+    public int getMaxConnectionAge();
+    public void setMaxConnectionAge( int maxConnectionAge ) throws NamingException;
+
+    public int getMaxIdleTimeExcessConnections();
+    public void setMaxIdleTimeExcessConnections( int maxIdleTimeExcessConnections ) throws NamingException;
+    
+    public int getMaxAdministrativeTaskTime();
+    public void setMaxAdministrativeTaskTime( int maxAdministrativeTaskTime ) throws NamingException;
+    
+    public int getCheckoutTimeout();
+    public void setCheckoutTimeout( int checkoutTimeout ) throws NamingException;
+	
+    public int getAcquireIncrement();
+    public void setAcquireIncrement( int acquireIncrement ) throws NamingException;
+	
+    public int getAcquireRetryAttempts();
+    public void setAcquireRetryAttempts( int acquireRetryAttempts ) throws NamingException;
+	
+    public int getAcquireRetryDelay();
+    public void setAcquireRetryDelay( int acquireRetryDelay ) throws NamingException;
+	
+    public boolean isAutoCommitOnClose();
+    public void setAutoCommitOnClose( boolean autoCommitOnClose ) throws NamingException;
+	
+    public String getConnectionTesterClassName();
+    public void setConnectionTesterClassName( String connectionTesterClassName ) throws PropertyVetoException, NamingException;
+	
+    public String getAutomaticTestTable();
+    public void setAutomaticTestTable( String automaticTestTable ) throws NamingException;
+	
+    public boolean isForceIgnoreUnresolvedTransactions();
+    public void setForceIgnoreUnresolvedTransactions( boolean forceIgnoreUnresolvedTransactions ) throws NamingException;
+	
+    public int getIdleConnectionTestPeriod();
+    public void setIdleConnectionTestPeriod( int idleConnectionTestPeriod ) throws NamingException;
+    
+    public int getInitialPoolSize();
+    public void setInitialPoolSize( int initialPoolSize ) throws NamingException;
+
+    public int getMaxIdleTime();
+    public void setMaxIdleTime( int maxIdleTime ) throws NamingException;
+	
+    public int getMaxPoolSize();
+    public void setMaxPoolSize( int maxPoolSize ) throws NamingException;
+	
+    public int getMaxStatements();
+    public void setMaxStatements( int maxStatements ) throws NamingException;
+	
+    public int getMaxStatementsPerConnection();
+    public void setMaxStatementsPerConnection( int maxStatementsPerConnection ) throws NamingException;
+	
+    public int getMinPoolSize();
+    public void setMinPoolSize( int minPoolSize ) throws NamingException;
+	
+    public int getPropertyCycle();
+    public void setPropertyCycle( int propertyCycle ) throws NamingException;
+    
+    public boolean isBreakAfterAcquireFailure();
+    public void setBreakAfterAcquireFailure( boolean breakAfterAcquireFailure ) throws NamingException;
+    
+    public boolean isTestConnectionOnCheckout();
+    public void setTestConnectionOnCheckout( boolean testConnectionOnCheckout ) throws NamingException;
+	
+    public boolean isTestConnectionOnCheckin();
+    public void setTestConnectionOnCheckin( boolean testConnectionOnCheckin ) throws NamingException;
+	
+    public boolean isUsesTraditionalReflectiveProxies();
+    public void setUsesTraditionalReflectiveProxies( boolean usesTraditionalReflectiveProxies ) throws NamingException;
+
+    public String getPreferredTestQuery();
+    public void setPreferredTestQuery( String preferredTestQuery ) throws NamingException;
+
+    // PoolBackedDataSource properties (count: 2)
+    public int getNumHelperThreads();
+    public void setNumHelperThreads( int numHelperThreads ) throws NamingException;
+
+    // shared properties (count: 1)
+    public String getFactoryClassLocation();
+    public void setFactoryClassLocation( String factoryClassLocation ) throws NamingException;
+
+    // PooledDataSource statistics
+
+    public int getNumUserPools() throws SQLException;
+
+    public int getNumConnectionsDefaultUser() throws SQLException;
+    public int getNumIdleConnectionsDefaultUser() throws SQLException;
+    public int getNumBusyConnectionsDefaultUser() throws SQLException;
+    public int getNumUnclosedOrphanedConnectionsDefaultUser() throws SQLException;
+
+    public int getNumConnections(String username, String password) throws SQLException;
+    public int getNumIdleConnections(String username, String password) throws SQLException;
+    public int getNumBusyConnections(String username, String password) throws SQLException;
+    public int getNumUnclosedOrphanedConnections(String username, String password) throws SQLException;
+    public float getEffectivePropertyCycle(String username, String password) throws SQLException;
+
+    public int getNumBusyConnectionsAllUsers() throws SQLException;
+    public int getNumIdleConnectionsAllUsers() throws SQLException;
+    public int getNumConnectionsAllUsers() throws SQLException;
+    public int getNumUnclosedOrphanedConnectionsAllUsers() throws SQLException;
+    public float getEffectivePropertyCycleDefaultUser() throws SQLException;
+
+    // PooledDataSource operations
+    public void softResetDefaultUser() throws SQLException;
+    public void softReset(String username, String password) throws SQLException;
+    public void softResetAllUsers() throws SQLException;
+    public void hardReset() throws SQLException;
+    public void close() throws SQLException;
+    
+    //JBoss only... (but these methods need not be called for the mbean to work)
+    public void create() throws Exception;
+    public void start() throws Exception;
+    public void stop();
+    public void destroy();
+}
diff --git a/src/classes/com/mchange/v2/c3p0/management/ActiveManagementCoordinator.java b/src/classes/com/mchange/v2/c3p0/management/ActiveManagementCoordinator.java
new file mode 100644
index 0000000..5d09175
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/management/ActiveManagementCoordinator.java
@@ -0,0 +1,151 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.management;
+
+import java.lang.management.*;
+import javax.management.*;
+import com.mchange.v2.log.*;
+import com.mchange.v2.c3p0.*;
+
+public class ActiveManagementCoordinator implements ManagementCoordinator
+{
+    private final static String C3P0_REGISTRY_NAME = "com.mchange.v2.c3p0:type=C3P0Registry";
+    
+    //MT: thread-safe
+    final static MLogger logger = MLog.getLogger( ActiveManagementCoordinator.class );
+
+    MBeanServer mbs;
+
+    public ActiveManagementCoordinator() throws Exception
+    {
+        this.mbs = ManagementFactory.getPlatformMBeanServer();
+    }
+
+    public void attemptManageC3P0Registry() 
+    {
+        try
+        {
+            ObjectName name = new ObjectName(C3P0_REGISTRY_NAME );
+            C3P0RegistryManager mbean = new C3P0RegistryManager();
+
+            if (mbs.isRegistered(name)) 
+            {
+                if (logger.isLoggable(MLevel.WARNING))
+                {
+                    logger.warning("A C3P0Registry mbean is already registered. " +
+                                    "This probably means that an application using c3p0 was undeployed, " +
+                                    "but not all PooledDataSources were closed prior to undeployment. " +
+                                    "This may lead to resource leaks over time. Please take care to close " +
+                                    "all PooledDataSources.");  
+                }
+                mbs.unregisterMBean(name);
+            }
+            mbs.registerMBean(mbean, name);
+        }
+        catch (Exception e)
+        { 
+            if ( logger.isLoggable( MLevel.WARNING ) )
+                logger.log( MLevel.WARNING, 
+                        "Failed to set up C3P0RegistryManager mBean. " +
+                        "[c3p0 will still function normally, but management via JMX may not be possible.]", 
+                        e);
+        }
+    }
+
+    public void attemptUnmanageC3P0Registry() 
+    {
+        try
+        {
+            ObjectName name = new ObjectName(C3P0_REGISTRY_NAME );
+            if (mbs.isRegistered(name))
+            {
+                mbs.unregisterMBean(name);
+                if (logger.isLoggable(MLevel.FINER))
+                    logger.log(MLevel.FINER, "C3P0Registry mbean unregistered.");
+            }
+            else if (logger.isLoggable(MLevel.FINE))
+                logger.fine("The C3P0Registry mbean was not found in the registry, so could not be unregistered.");   
+        }
+        catch (Exception e)
+        {
+            if ( logger.isLoggable( MLevel.WARNING ) )
+                logger.log( MLevel.WARNING, 
+                        "An Exception occurred while trying to unregister the C3P0RegistryManager mBean." +
+                        e);
+        }
+    }
+    
+    public void attemptManagePooledDataSource(PooledDataSource pds) 
+    {
+        String name = getPdsObjectNameStr( pds );
+        try
+        {
+            //PooledDataSourceManager mbean = new PooledDataSourceManager( pds );
+            //mbs.registerMBean(mbean, ObjectName.getInstance(name));
+            //if (logger.isLoggable(MLevel.FINER))
+            //    logger.log(MLevel.FINER, "MBean: " + name + " registered.");
+
+            // DynamicPooledDataSourceManagerMBean registers itself on construction (and logs its own registration)
+            DynamicPooledDataSourceManagerMBean mbean = new DynamicPooledDataSourceManagerMBean( pds, name, mbs );
+        }
+        catch (Exception e)
+        { 
+            if ( logger.isLoggable( MLevel.WARNING ) )
+                logger.log( MLevel.WARNING, 
+                        "Failed to set up a PooledDataSourceManager mBean. [" + name + "] " +
+                        "[c3p0 will still functioning normally, but management via JMX may not be possible.]", 
+                        e);
+        }
+    }
+   
+    
+    public void attemptUnmanagePooledDataSource(PooledDataSource pds) 
+    {
+        String nameStr = getPdsObjectNameStr( pds );
+        try
+        {
+            ObjectName name = new ObjectName( nameStr );
+            if (mbs.isRegistered(name))
+            {
+                mbs.unregisterMBean(name);
+                if (logger.isLoggable(MLevel.FINER))
+                    logger.log(MLevel.FINER, "MBean: " + nameStr + " unregistered.");
+            }
+            else 
+                if (logger.isLoggable(MLevel.FINE))
+                    logger.fine("The mbean " + nameStr + " was not found in the registry, so could not be unregistered.");   
+        }
+        catch (Exception e)
+        { 
+            if ( logger.isLoggable( MLevel.WARNING ) )
+                logger.log( MLevel.WARNING, 
+                        "An Exception occurred while unregistering mBean. [" + nameStr + "] " +
+                        e);
+        }
+    }
+    
+    private String getPdsObjectNameStr(PooledDataSource pds)
+    { return "com.mchange.v2.c3p0:type=PooledDataSource[" + pds.getIdentityToken() + "]"; }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/management/C3P0RegistryManager.java b/src/classes/com/mchange/v2/c3p0/management/C3P0RegistryManager.java
new file mode 100644
index 0000000..519a3fb
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/management/C3P0RegistryManager.java
@@ -0,0 +1,77 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.management;
+
+import java.util.*;
+import java.sql.SQLException;
+import com.mchange.v2.c3p0.C3P0Registry;
+import com.mchange.v2.c3p0.subst.C3P0Substitutions;
+
+public class C3P0RegistryManager implements C3P0RegistryManagerMBean 
+{
+    public String[] getAllIdentityTokens()
+    { 
+        Set tokens = C3P0Registry.allIdentityTokens(); 
+        return (String[]) tokens.toArray( new String[ tokens.size() ] );
+    }
+
+    public Set getAllIdentityTokenized()
+    { return C3P0Registry.allIdentityTokenized(); }
+
+    public Set getAllPooledDataSources()
+    { return C3P0Registry.allPooledDataSources(); }
+
+    public int getAllIdentityTokenCount()
+    { return C3P0Registry.allIdentityTokens().size(); }
+
+    public int getAllIdentityTokenizedCount()
+    { return C3P0Registry.allIdentityTokenized().size(); }
+
+    public int getAllPooledDataSourcesCount()
+    { return C3P0Registry.allPooledDataSources().size(); }
+
+    public String[] getAllIdentityTokenizedStringified()
+    { return stringifySet( C3P0Registry.allIdentityTokenized() ); }
+
+    public String[] getAllPooledDataSourcesStringified()
+    { return stringifySet( C3P0Registry.allPooledDataSources() ); }
+
+    public int getNumPooledDataSources() throws SQLException
+    { return C3P0Registry.getNumPooledDataSources(); }
+
+    public int getNumPoolsAllDataSources() throws SQLException
+    { return C3P0Registry.getNumPoolsAllDataSources(); }
+    
+    public String getC3p0Version()
+    { return C3P0Substitutions.VERSION ; }
+
+    private String[] stringifySet(Set s)
+    {
+	String[] out = new String[ s.size() ];
+	int i = 0;
+	for (Iterator ii = s.iterator(); ii.hasNext(); )
+	    out[i++] = ii.next().toString();
+	return out;
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/management/C3P0RegistryManagerMBean.java b/src/classes/com/mchange/v2/c3p0/management/C3P0RegistryManagerMBean.java
new file mode 100644
index 0000000..de7d10d
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/management/C3P0RegistryManagerMBean.java
@@ -0,0 +1,46 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.management;
+
+import java.sql.SQLException;
+import java.util.Set;
+
+public interface C3P0RegistryManagerMBean
+{
+    public String[] getAllIdentityTokens();
+    public Set getAllIdentityTokenized();
+    public Set getAllPooledDataSources();
+
+    public int getAllIdentityTokenCount();
+    public int getAllIdentityTokenizedCount();
+    public int getAllPooledDataSourcesCount();
+
+    public String[] getAllIdentityTokenizedStringified();
+    public String[] getAllPooledDataSourcesStringified();
+
+    public int getNumPooledDataSources() throws SQLException;
+    public int getNumPoolsAllDataSources() throws SQLException;
+    
+    public String getC3p0Version();
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/management/DynamicPooledDataSourceManagerMBean.java b/src/classes/com/mchange/v2/c3p0/management/DynamicPooledDataSourceManagerMBean.java
new file mode 100644
index 0000000..c7460a9
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/management/DynamicPooledDataSourceManagerMBean.java
@@ -0,0 +1,619 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.management;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.util.*;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.DataSource;
+
+import com.mchange.v1.lang.ClassUtils;
+import com.mchange.v2.c3p0.ComboPooledDataSource;
+import com.mchange.v2.c3p0.DriverManagerDataSource;
+import com.mchange.v2.c3p0.PooledDataSource;
+import com.mchange.v2.c3p0.PoolBackedDataSource;
+import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
+import com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.management.ManagementUtils;
+
+public class DynamicPooledDataSourceManagerMBean implements DynamicMBean
+{
+    final static MLogger logger = MLog.getLogger( DynamicPooledDataSourceManagerMBean.class );
+
+    final static Set HIDE_PROPS;
+    final static Set HIDE_OPS;
+    final static Set FORCE_OPS;
+    
+    final static Set FORCE_READ_ONLY_PROPS;
+
+    static
+    {
+        Set hpTmp = new HashSet();
+        hpTmp.add("connectionPoolDataSource");
+        hpTmp.add("nestedDataSource");
+        hpTmp.add("reference");
+        hpTmp.add("connection");
+        hpTmp.add("password");
+        hpTmp.add("pooledConnection");
+        hpTmp.add("properties");
+        hpTmp.add("logWriter");
+        hpTmp.add("lastAcquisitionFailureDefaultUser");
+        hpTmp.add("lastCheckoutFailureDefaultUser");
+        hpTmp.add("lastCheckinFailureDefaultUser");
+        hpTmp.add("lastIdleTestFailureDefaultUser");
+        hpTmp.add("lastConnectionTestFailureDefaultUser");
+        HIDE_PROPS = Collections.unmodifiableSet( hpTmp );
+        
+	Class[] userPassArgs = new Class[] { String.class, String.class };
+        Set hoTmp = new HashSet();
+        try
+        {
+            hoTmp.add(PooledDataSource.class.getMethod("close", new Class[] { boolean.class }) );
+            hoTmp.add(PooledDataSource.class.getMethod("getConnection", userPassArgs ) );
+
+            hoTmp.add(PooledDataSource.class.getMethod("getLastAcquisitionFailure", userPassArgs ) );
+            hoTmp.add(PooledDataSource.class.getMethod("getLastCheckinFailure", userPassArgs ) );
+            hoTmp.add(PooledDataSource.class.getMethod("getLastCheckoutFailure", userPassArgs ) );
+            hoTmp.add(PooledDataSource.class.getMethod("getLastIdleTestFailure", userPassArgs ) );
+            hoTmp.add(PooledDataSource.class.getMethod("getLastConnectionTestFailure", userPassArgs ) );
+        }
+        catch (Exception e)
+        {
+            logger.log(MLevel.WARNING, "Tried to hide an operation from being exposed by mbean, but failed to find the operation!", e);
+        }
+        HIDE_OPS = Collections.unmodifiableSet(hoTmp);
+        
+        Set fropTmp = new HashSet();
+        fropTmp.add("identityToken");
+        FORCE_READ_ONLY_PROPS = Collections.unmodifiableSet(fropTmp);
+
+	Set foTmp = new HashSet();
+	FORCE_OPS = Collections.unmodifiableSet(foTmp);
+    }
+
+    final static MBeanOperationInfo[] OP_INFS = extractOpInfs();
+
+    MBeanInfo info = null;
+
+    PooledDataSource pds;
+    String mbeanName;
+    MBeanServer mbs;
+    
+    ConnectionPoolDataSource cpds;
+    DataSource unpooledDataSource;
+
+    //attr names to attr infos
+    Map pdsAttrInfos;               
+    Map cpdsAttrInfos;              
+    Map unpooledDataSourceAttrInfos;
+    
+    PropertyChangeListener pcl = new PropertyChangeListener()
+    {
+        public void propertyChange(PropertyChangeEvent evt)
+        {
+            String propName = evt.getPropertyName();
+            Object val = evt.getNewValue();
+
+            if ("nestedDataSource".equals(propName) || "connectionPoolDataSource".equals(propName))
+                reinitialize();
+        }
+    };
+
+    public DynamicPooledDataSourceManagerMBean(PooledDataSource pds, String mbeanName, MBeanServer mbs)
+        throws Exception
+    { 
+        this.pds = pds; 
+        this.mbeanName = mbeanName;
+        this.mbs = mbs;
+        
+        if (pds instanceof ComboPooledDataSource)
+            /* do nothing */;
+        else if (pds instanceof AbstractPoolBackedDataSource)
+            ((AbstractPoolBackedDataSource) pds).addPropertyChangeListener(pcl);
+        else
+            logger.warning(this + "managing an unexpected PooledDataSource. Only top-level attributes will be available. PooledDataSource: " + pds);
+        
+        Exception e = reinitialize();
+        if (e != null) 
+            throw e;
+    }
+    
+    private synchronized Exception reinitialize()
+    {
+        try
+        {
+            // for ComboPooledDataSource, everything we care about is exposed via the PooledDataSource
+            // for other implementations, we have to pay attention to nested DataSources
+            if (!(pds instanceof ComboPooledDataSource) && pds instanceof AbstractPoolBackedDataSource)
+            {
+                if (this.cpds instanceof WrapperConnectionPoolDataSource) //implies non-null, this is a reinit
+                    ((WrapperConnectionPoolDataSource) this.cpds).removePropertyChangeListener(pcl);
+                
+                
+                // yeah, we reassign instantly, but for my comfort...
+                this.cpds = null;
+                this.unpooledDataSource = null;
+                
+                this.cpds = ((AbstractPoolBackedDataSource) pds).getConnectionPoolDataSource();
+
+                if (cpds instanceof WrapperConnectionPoolDataSource)
+                {
+                    this.unpooledDataSource = ((WrapperConnectionPoolDataSource) cpds).getNestedDataSource();
+                    ((WrapperConnectionPoolDataSource) this.cpds).addPropertyChangeListener(pcl);
+                }
+            }
+
+            pdsAttrInfos = extractAttributeInfos( pds );
+            cpdsAttrInfos = extractAttributeInfos( cpds );
+            unpooledDataSourceAttrInfos = extractAttributeInfos( unpooledDataSource );
+
+            Set allAttrNames = new HashSet();
+            allAttrNames.addAll(pdsAttrInfos.keySet());
+            allAttrNames.addAll(cpdsAttrInfos.keySet());
+            allAttrNames.addAll(unpooledDataSourceAttrInfos.keySet());
+
+            Set allAttrs = new HashSet();
+            for(Iterator ii = allAttrNames.iterator(); ii.hasNext();)
+            {
+                String name = (String) ii.next();
+                Object attrInfo;
+                attrInfo = pdsAttrInfos.get(name);
+                if (attrInfo == null)
+                    attrInfo = cpdsAttrInfos.get(name);
+                if (attrInfo == null)
+                    attrInfo = unpooledDataSourceAttrInfos.get(name);
+                allAttrs.add(attrInfo);
+            }
+
+            String className = this.getClass().getName();
+            MBeanAttributeInfo[] attrInfos = (MBeanAttributeInfo[]) allAttrs.toArray(new MBeanAttributeInfo[ allAttrs.size() ]);
+            Class[] ctorArgClasses = {PooledDataSource.class, String.class, MBeanServer.class};
+            MBeanConstructorInfo[] constrInfos 
+               = new MBeanConstructorInfo[] { new MBeanConstructorInfo("Constructor from PooledDataSource", this.getClass().getConstructor(ctorArgClasses)) };
+            this.info = new MBeanInfo( this.getClass().getName(),
+                            "An MBean to monitor and manage a PooledDataSource",
+                            attrInfos,
+                            constrInfos,
+                            OP_INFS,
+                            null);
+            
+            // we need to reregister when the attributes we support may have changed, to be sure
+            // that the MBeanInfo is reread.
+            try
+            {
+                ObjectName oname = ObjectName.getInstance( mbeanName );
+                if (mbs.isRegistered( oname ))
+                {
+                    mbs.unregisterMBean( oname );
+                    if (logger.isLoggable(MLevel.FINER))
+                        logger.log(MLevel.FINER, "MBean: " + mbeanName + " unregistered, in order to be reregistered after update.");
+                }
+                mbs.registerMBean( this, oname );
+                if (logger.isLoggable(MLevel.FINER))
+                    logger.log(MLevel.FINER, "MBean: " + mbeanName + " registered.");
+                
+                return null;
+            }
+            catch (Exception e)
+            {
+                if ( logger.isLoggable(MLevel.WARNING) )
+                    logger.log(MLevel.WARNING, 
+                               "An Exception occurred while registering/reregistering mbean " + mbeanName +
+                               ". MBean may not be registered, or may not work properly.",
+                               e );
+                return e;
+            }
+        }
+        catch (NoSuchMethodException e)
+        {
+            if (logger.isLoggable(MLevel.SEVERE))
+                logger.log( MLevel.SEVERE,
+                                "Huh? We can't find our own constructor?? The one we're in?",
+                                e);
+            return e;
+        }
+    }
+
+    // this method is fragile, makes assumptions that may have to change with
+    // the PooledDataSource interface. It presumes that methods that look like
+    // JavaBean properties should be skipped as attributes, that methods with
+    // two string arguments are always username and password, that methods with
+    // a return value are simple getters, while void methods are modifiers. At the
+    // time of this writing, these assumptions all hold for PooledDataSource.
+    // But beware the future.
+    private static MBeanOperationInfo[] extractOpInfs()
+    {
+        MBeanParameterInfo user = new MBeanParameterInfo("user", "java.lang.String", "The database username of a pool-owner.");
+        MBeanParameterInfo pwd = new MBeanParameterInfo("password", "java.lang.String", "The database password of a pool-owner.");
+        MBeanParameterInfo[] userPass = {user, pwd};
+        MBeanParameterInfo[] empty = {};
+
+        Method[] meths = PooledDataSource.class.getMethods();
+        Set attrInfos = new TreeSet(ManagementUtils.OP_INFO_COMPARATOR);
+
+        for (int i = 0; i < meths.length; ++i)
+        {
+            Method meth = meths[i];
+            if (HIDE_OPS.contains(meth))
+                continue;
+            
+            String mname = meth.getName();
+            Class[] params = meth.getParameterTypes();
+
+	    if (! FORCE_OPS.contains(mname))
+		{
+		    //get rid of things we'd have picked up as attributes
+		    if (mname.startsWith("set") && params.length == 1)
+			continue;
+		    if ((mname.startsWith("get") || mname.startsWith("is")) && params.length == 0)
+			continue;
+		}
+
+            Class retType = meth.getReturnType();
+            int impact = (retType == void.class ? MBeanOperationInfo.ACTION : MBeanOperationInfo.INFO);
+            MBeanParameterInfo[] pi;
+            if (params.length == 2 && params[0] == String.class && params[1] == String.class)
+                pi = userPass;
+            else if (params.length == 0)
+                pi = empty;
+            else
+                pi = null;
+
+            MBeanOperationInfo opi;
+            if (pi != null)
+                opi = new MBeanOperationInfo( mname, // name
+                                null,  // desc
+                                pi,
+                                retType.getName(),
+                                impact );
+            else
+            {
+                //System.err.println("autobuilding opi from meth " + meth);
+                opi = new MBeanOperationInfo(meth.toString(), meth);
+            }
+
+            //System.err.println("Created MBeanOperationInfo: " + opi + " [" + opi.getName() + ']');
+            attrInfos.add( opi );
+        }
+
+        return (MBeanOperationInfo[]) attrInfos.toArray( new MBeanOperationInfo[ attrInfos.size() ] );
+    }
+
+    public synchronized Object getAttribute(String attr) throws AttributeNotFoundException, MBeanException, ReflectionException
+    {
+        try
+        {
+            AttrRec rec = attrRecForAttribute(attr);
+            if (rec == null)
+                throw new AttributeNotFoundException(attr);
+            else
+            {
+                MBeanAttributeInfo ai = rec.attrInfo;
+                if (! ai.isReadable() )
+                    throw new IllegalArgumentException(attr + " not readable.");
+                else
+                {
+                    String name = ai.getName();
+                    String pfx = ai.isIs() ? "is" : "get";
+                    String mname = pfx + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+                    Object target = rec.target; 
+                    Method m = target.getClass().getMethod(mname, null);
+                    return m.invoke(target, null);
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            if (logger.isLoggable(MLevel.WARNING))
+                logger.log(MLevel.WARNING, "Failed to get requested attribute: " + attr, e);
+            throw new MBeanException(e);
+        }
+    }
+
+    public synchronized AttributeList getAttributes(String[] attrs)
+    {
+        AttributeList al = new AttributeList();
+        for (int i = 0, len = attrs.length; i < len; ++i)
+        {
+            String attr = attrs[i];
+            try
+            {
+                Object val = getAttribute(attr);
+                al.add(new Attribute(attr, val));
+            }
+            catch (Exception e)
+            {
+                if (logger.isLoggable(MLevel.WARNING))
+                    logger.log(MLevel.WARNING, "Failed to get requested attribute (for list): " + attr, e);
+            }
+        }
+        return al;
+    }
+
+    private AttrRec attrRecForAttribute(String attr)
+    {
+        assert (Thread.holdsLock(this));
+        
+        if (pdsAttrInfos.containsKey(attr))
+            return new AttrRec(pds, (MBeanAttributeInfo) pdsAttrInfos.get(attr));
+        else if (cpdsAttrInfos.containsKey(attr))
+            return new AttrRec(cpds, (MBeanAttributeInfo) cpdsAttrInfos.get(attr));
+        else if (unpooledDataSourceAttrInfos.containsKey(attr))
+            return new AttrRec(unpooledDataSource, (MBeanAttributeInfo) unpooledDataSourceAttrInfos.get(attr));
+        else
+            return null;
+    }
+
+    public synchronized MBeanInfo getMBeanInfo()
+    { 
+        if (info == null)
+            reinitialize();
+        return info; 
+    }
+
+    public synchronized Object invoke(String operation, Object[] paramVals, String[] signature) throws MBeanException, ReflectionException
+    {
+        try
+        {
+            int slen = signature.length;
+            Class[] paramTypes = new Class[ slen ];
+            for (int i = 0; i < slen; ++i)
+                paramTypes[i] = ClassUtils.forName( signature[i] );
+            
+            //all operations should be on pds
+            Method m = pds.getClass().getMethod(operation, paramTypes);
+            return m.invoke(pds, paramVals);
+        }
+        catch (NoSuchMethodException e)
+        {
+            // although not generally legal as of the latest JMX spec,
+            // someone could be trying to work with attributes through
+            // invoke. If so, we try to deal
+            try
+            {
+            boolean two = false;
+            if (signature.length == 0 && ( operation.startsWith("get") || (two = operation.startsWith("is")) ))
+            {
+                int i = two ? 2 : 3;
+                String attr = Character.toLowerCase(operation.charAt(i)) + operation.substring(i + 1);
+                return getAttribute( attr );
+            }
+            else if (signature.length == 1 && operation.startsWith("set"))
+            {
+                setAttribute(new Attribute(Character.toLowerCase(operation.charAt(3)) + operation.substring(4), paramVals[0]));
+                return null;
+            }
+            else
+                throw new MBeanException(e);
+            }
+            catch (Exception e2)
+            { throw new MBeanException(e2); }
+        }
+        catch (Exception e)
+        { throw new MBeanException(e); }
+    }
+
+    public synchronized void setAttribute(Attribute attrObj) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException
+    {
+        try
+        {
+            String attr = attrObj.getName();
+            
+            if (attr == "factoryClassLocation") // special case
+            {
+                if (pds instanceof ComboPooledDataSource)
+                {
+                    ((ComboPooledDataSource) pds).setFactoryClassLocation((String) attrObj.getValue());
+                    return;
+                }
+                else if (pds instanceof AbstractPoolBackedDataSource)
+                {
+                    String val = (String) attrObj.getValue();
+                    AbstractPoolBackedDataSource apbds = ((AbstractPoolBackedDataSource) pds);
+                    apbds.setFactoryClassLocation( val );
+                    ConnectionPoolDataSource checkDs1 = apbds.getConnectionPoolDataSource();
+                    if (checkDs1 instanceof WrapperConnectionPoolDataSource)
+                    {
+                        WrapperConnectionPoolDataSource wcheckDs1 = (WrapperConnectionPoolDataSource) checkDs1;
+                        wcheckDs1.setFactoryClassLocation( val );
+                        DataSource checkDs2 = wcheckDs1.getNestedDataSource();
+                        if (checkDs2 instanceof DriverManagerDataSource)
+                            ((DriverManagerDataSource) checkDs2).setFactoryClassLocation( val );
+                    }
+                    return;
+                }
+                // else try treating factoryClassLocation like any other attribute
+                // on the presumption that some future, unexpected DataSource that
+                // exposes this property will not require the property to be set at
+                // multiple levels, as PoolBackedDataSource does...
+            }
+            
+            AttrRec rec = attrRecForAttribute(attr);
+            if (rec == null)
+                throw new AttributeNotFoundException(attr);
+            else
+            {
+                MBeanAttributeInfo ai = rec.attrInfo;
+                if (! ai.isWritable() )
+                    throw new IllegalArgumentException(attr + " not writable.");
+                else
+                {
+                    Class attrType = ClassUtils.forName( rec.attrInfo.getType() );
+                    String name = ai.getName();
+                    String pfx = "set";
+                    String mname = pfx + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+                    Object target = rec.target; 
+                    Method m = target.getClass().getMethod(mname, new Class[] {attrType});
+                    m.invoke(target, new Object[] { attrObj.getValue() });
+                    
+                    // if we were unable to set this attribute directly in the PooledDataSource,
+                    // we are updating a property of a nested DataSource, and we should reset
+                    // the pool manager of the PooledDataSource implementation so that these
+                    // properties are reread and the changes take effect.
+                    if (target != pds)
+                    {
+                         if (pds instanceof AbstractPoolBackedDataSource)
+                            ((AbstractPoolBackedDataSource) pds).resetPoolManager(false);
+                         else if (logger.isLoggable(MLevel.WARNING))
+                             logger.warning("MBean set a nested ConnectionPoolDataSource or DataSource parameter on an unknown PooledDataSource type. " + 
+                                             "Could not reset the pool manager, so the changes may not take effect. " + "" +
+                                              "c3p0 may need to be updated for PooledDataSource type " + pds.getClass() + ".");
+                             
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            if (logger.isLoggable(MLevel.WARNING))
+                logger.log(MLevel.WARNING, "Failed to set requested attribute: " + attrObj, e);
+            throw new MBeanException(e);
+        }
+    }
+
+    public synchronized AttributeList setAttributes(AttributeList al)
+    {
+        AttributeList out = new AttributeList();
+        for (int i = 0, len = al.size(); i < len; ++i)
+        {
+            Attribute attrObj = (Attribute) al.get(i);
+            
+            try
+            {
+                this.setAttribute( attrObj );
+                out.add(attrObj);
+            }
+            catch (Exception e)
+            {
+                if (logger.isLoggable(MLevel.WARNING))
+                    logger.log(MLevel.WARNING, "Failed to set requested attribute (from list): " + attrObj, e);
+            }
+        }
+        return out;
+    }
+
+    private static Map extractAttributeInfos(Object bean)
+    {
+        if ( bean != null)
+        {
+            try
+            {
+                Map out = new HashMap();
+                BeanInfo beanInfo = Introspector.getBeanInfo( bean.getClass(), Object.class ); //so we don't see getClass() as a property
+                PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
+                //System.err.println("ignoreProps: " + ignoreProps );
+                for( int i = 0, len = pds.length; i < len; ++i)
+                {
+                    PropertyDescriptor pd = pds[i];
+
+                    String name;
+                    String desc;
+                    Method getter;
+                    Method setter;
+
+                    name = pd.getName();
+
+                    if (HIDE_PROPS.contains( name ))
+                        continue;
+
+                    desc = getDescription( name );
+                    getter = pd.getReadMethod();
+                    setter = pd.getWriteMethod();
+                    
+                    if (FORCE_READ_ONLY_PROPS.contains(name))
+                        setter = null;
+
+                    /*
+                     * Note that it's not a problem that these
+                     * getters and setters are not against this class
+                     * the MBeanAttributInfo just uses the method
+                     * names and attribute type to construct itself,
+                     * and does not hold the methods themselves for
+                     * future invocation.
+                     */
+
+                    try
+                    {
+                        out.put( name, new MBeanAttributeInfo(name, desc, getter, setter) );
+                    }
+                    catch (javax.management.IntrospectionException e)
+                    {
+                        if (logger.isLoggable( MLevel.WARNING ))
+                            logger.log( MLevel.WARNING, "IntrospectionException while setting up MBean attribute '" + name + "'", e);
+                    }
+                }
+
+                return Collections.synchronizedMap(out);
+            }
+            catch (java.beans.IntrospectionException e)
+            {
+                if (logger.isLoggable( MLevel.WARNING ))
+                    logger.log( MLevel.WARNING, "IntrospectionException while setting up MBean attributes for " + bean, e);
+                return Collections.EMPTY_MAP;
+            }
+        }
+        else
+            return Collections.EMPTY_MAP;
+    }
+
+    // TODO: use a ResourceBundle for attribute descriptions.
+    // (Extra credit -- build from xml file, build docs same way)
+    private static String getDescription(String attrName)
+    { return null; }
+
+    private static class AttrRec
+    {
+        Object target;
+        MBeanAttributeInfo attrInfo;
+    
+        AttrRec(Object target, MBeanAttributeInfo attrInfo)
+        {
+            this.target = target;
+            this.attrInfo = attrInfo;
+        }
+    }
+
+}
diff --git a/src/classes/com/mchange/v2/c3p0/management/ManagementCoordinator.java b/src/classes/com/mchange/v2/c3p0/management/ManagementCoordinator.java
new file mode 100644
index 0000000..32fda97
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/management/ManagementCoordinator.java
@@ -0,0 +1,35 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.management;
+
+import com.mchange.v2.c3p0.PooledDataSource;
+
+public interface ManagementCoordinator
+{
+    public void attemptManageC3P0Registry();
+    public void attemptUnmanageC3P0Registry();
+    public void attemptManagePooledDataSource(PooledDataSource pds);
+    public void attemptUnmanagePooledDataSource( PooledDataSource pds );
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/management/NullManagementCoordinator.java b/src/classes/com/mchange/v2/c3p0/management/NullManagementCoordinator.java
new file mode 100644
index 0000000..d3fe25f
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/management/NullManagementCoordinator.java
@@ -0,0 +1,42 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.management;
+
+import com.mchange.v2.c3p0.PooledDataSource;
+
+public class NullManagementCoordinator implements ManagementCoordinator
+{
+    public void attemptManageC3P0Registry() 
+    {}
+
+    public void attemptUnmanageC3P0Registry()
+    {}
+    
+    public void attemptManagePooledDataSource(PooledDataSource pds) 
+    {}
+    
+    public void attemptUnmanagePooledDataSource( PooledDataSource pds )
+    {}
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/management/PooledDataSourceManager.java b/src/classes/com/mchange/v2/c3p0/management/PooledDataSourceManager.java
new file mode 100644
index 0000000..718bf84
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/management/PooledDataSourceManager.java
@@ -0,0 +1,126 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.management;
+
+import java.sql.SQLException;
+import java.util.Collection;
+import com.mchange.v2.c3p0.PooledDataSource;
+
+public class PooledDataSourceManager implements PooledDataSourceManagerMBean
+{
+    PooledDataSource pds;
+
+    public PooledDataSourceManager( PooledDataSource pds )
+    { this.pds = pds; }
+
+    public String getIdentityToken()
+    { return pds.getIdentityToken(); }
+
+    public String getDataSourceName()
+    { return pds.getDataSourceName(); }
+
+    public void setDataSourceName(String dataSourceName)
+    { pds.setDataSourceName( dataSourceName ); }
+
+    public int getNumConnectionsDefaultUser() throws SQLException
+    { return pds.getNumConnectionsDefaultUser(); }
+
+    public int getNumIdleConnectionsDefaultUser() throws SQLException
+    { return pds.getNumIdleConnectionsDefaultUser(); }
+
+    public int getNumBusyConnectionsDefaultUser() throws SQLException
+    { return pds.getNumBusyConnectionsDefaultUser(); }
+
+    public int getNumUnclosedOrphanedConnectionsDefaultUser() throws SQLException
+    { return pds.getNumUnclosedOrphanedConnectionsDefaultUser(); }
+
+    public float getEffectivePropertyCycleDefaultUser() throws SQLException
+    { return pds.getEffectivePropertyCycleDefaultUser(); }
+
+    public int getThreadPoolSize() throws SQLException
+    { return pds.getThreadPoolSize(); }
+
+    public int getThreadPoolNumActiveThreads() throws SQLException
+    { return pds.getThreadPoolNumActiveThreads(); }
+
+    public int getThreadPoolNumIdleThreads() throws SQLException
+    { return pds.getThreadPoolNumIdleThreads(); }
+
+    public int getThreadPoolNumTasksPending() throws SQLException
+    { return pds.getThreadPoolNumTasksPending(); }
+
+    public String sampleThreadPoolStackTraces() throws SQLException
+    { return pds.sampleThreadPoolStackTraces(); }
+
+    public String sampleThreadPoolStatus() throws SQLException
+    { return pds.sampleThreadPoolStatus(); }
+
+    public void softResetDefaultUser() throws SQLException
+    { pds.softResetDefaultUser(); }
+
+    public int getNumConnections(String username, String password) throws SQLException
+    { return pds.getNumConnections( username, password ); }
+
+    public int getNumIdleConnections(String username, String password) throws SQLException
+    { return pds.getNumIdleConnections( username, password ); }
+
+    public int getNumBusyConnections(String username, String password) throws SQLException
+    { return pds.getNumBusyConnections( username, password ); }
+
+    public int getNumUnclosedOrphanedConnections(String username, String password) throws SQLException
+    { return pds.getNumUnclosedOrphanedConnections( username, password ); }
+
+    public float getEffectivePropertyCycle(String username, String password) throws SQLException
+    { return pds.getEffectivePropertyCycle( username, password ); }
+
+    public void softReset(String username, String password) throws SQLException
+    { pds.softReset( username, password ); }
+
+    public int getNumBusyConnectionsAllUsers() throws SQLException
+    { return pds.getNumBusyConnectionsAllUsers(); }
+
+    public int getNumIdleConnectionsAllUsers() throws SQLException
+    { return pds.getNumIdleConnectionsAllUsers(); }
+
+    public int getNumConnectionsAllUsers() throws SQLException
+    { return pds.getNumConnectionsAllUsers(); }
+
+    public int getNumUnclosedOrphanedConnectionsAllUsers() throws SQLException
+    { return pds.getNumUnclosedOrphanedConnectionsAllUsers(); }
+
+    public void softResetAllUsers() throws SQLException
+    { pds.softResetAllUsers(); }
+
+    public int getNumUserPools() throws SQLException
+    { return pds.getNumUserPools(); }
+
+    public Collection getAllUsers() throws SQLException
+    { return pds.getAllUsers(); }
+
+    public void hardReset() throws SQLException
+    { pds.hardReset(); }
+
+    public void close() throws SQLException
+    { pds.close(); }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/management/PooledDataSourceManagerMBean.java b/src/classes/com/mchange/v2/c3p0/management/PooledDataSourceManagerMBean.java
new file mode 100644
index 0000000..9f3b2ec
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/management/PooledDataSourceManagerMBean.java
@@ -0,0 +1,61 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.management;
+
+import java.sql.SQLException;
+import java.util.Collection;
+
+public interface PooledDataSourceManagerMBean
+{
+    public String getIdentityToken();
+    public String getDataSourceName();
+    public void setDataSourceName(String dataSourceName);
+    public int getNumConnectionsDefaultUser() throws SQLException;
+    public int getNumIdleConnectionsDefaultUser() throws SQLException;
+    public int getNumBusyConnectionsDefaultUser() throws SQLException;
+    public int getNumUnclosedOrphanedConnectionsDefaultUser() throws SQLException;
+    public float getEffectivePropertyCycleDefaultUser() throws SQLException;
+    public void softResetDefaultUser() throws SQLException;
+    public int getNumConnections(String username, String password) throws SQLException;
+    public int getNumIdleConnections(String username, String password) throws SQLException;
+    public int getNumBusyConnections(String username, String password) throws SQLException;
+    public int getNumUnclosedOrphanedConnections(String username, String password) throws SQLException;
+    public float getEffectivePropertyCycle(String username, String password) throws SQLException;
+    public void softReset(String username, String password) throws SQLException;
+    public int getNumBusyConnectionsAllUsers() throws SQLException;
+    public int getNumIdleConnectionsAllUsers() throws SQLException;
+    public int getNumConnectionsAllUsers() throws SQLException;
+    public int getNumUnclosedOrphanedConnectionsAllUsers() throws SQLException;
+    public int getThreadPoolSize() throws SQLException;
+    public int getThreadPoolNumActiveThreads() throws SQLException;
+    public int getThreadPoolNumIdleThreads() throws SQLException;
+    public int getThreadPoolNumTasksPending() throws SQLException;
+    public String sampleThreadPoolStackTraces() throws SQLException;
+    public String sampleThreadPoolStatus() throws SQLException;
+    public void softResetAllUsers() throws SQLException;
+    public int getNumUserPools() throws SQLException;
+    public Collection getAllUsers() throws SQLException;
+    public void hardReset() throws SQLException;
+    public void close() throws SQLException;
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/mbean/C3P0PooledDataSource.java b/src/classes/com/mchange/v2/c3p0/mbean/C3P0PooledDataSource.java
new file mode 100644
index 0000000..9d0d53b
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/mbean/C3P0PooledDataSource.java
@@ -0,0 +1,433 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.mbean;
+
+import com.mchange.v2.c3p0.*;
+import com.mchange.v2.log.*;
+import java.beans.PropertyVetoException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.io.PrintWriter;
+import java.util.Properties;
+import javax.sql.DataSource;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.Context;
+import javax.naming.NameAlreadyBoundException;
+import javax.naming.NamingException;
+
+/**
+ * @deprecated Please use com.mchange.v2.c3p0.jboss.C3P0PooledDataSource
+ */
+public class C3P0PooledDataSource implements C3P0PooledDataSourceMBean
+{
+    private final static MLogger logger = MLog.getLogger( C3P0PooledDataSource.class );
+
+    String jndiName;
+
+    ComboPooledDataSource combods = new ComboPooledDataSource();
+
+    private void rebind() throws NamingException
+    { rebind(null); }
+
+    private void rebind(String unbindName) throws NamingException
+    {
+	InitialContext ictx = new InitialContext();
+	if (unbindName != null)
+	    ictx.unbind( unbindName );
+	
+	if (jndiName != null)
+	{
+	    // Thanks to David D. Kilzer for this code to auto-create
+	    // subcontext paths!
+	    Name name = ictx.getNameParser( jndiName ).parse( jndiName );
+	    Context ctx = ictx;
+	    for (int i = 0, max = name.size() - 1; i < max; i++)
+	    {
+		try
+		{ ctx = ctx.createSubcontext( name.get( i ) ); }
+		catch (NameAlreadyBoundException ignore)
+		{ ctx = (Context) ctx.lookup( name.get( i ) ); }
+	    }
+
+ 	    ictx.rebind( jndiName, combods );
+	}
+
+
+    }
+
+    // Jndi Setup Names
+    public void setJndiName(String jndiName) throws NamingException
+    {
+	String unbindName = this.jndiName;
+	this.jndiName = jndiName; 
+	rebind( unbindName );
+    }
+
+    public String getJndiName()
+    { return jndiName; }
+
+    // DriverManagerDataSourceProperties  (count: 4)
+    public String getDescription()
+    { return combods.getDescription(); }
+	
+    public void setDescription( String description ) throws NamingException
+    { 
+	combods.setDescription( description ); 
+	rebind();
+    }
+	
+    public String getDriverClass()
+    { return combods.getDriverClass(); }
+	
+    public void setDriverClass( String driverClass ) throws PropertyVetoException, NamingException
+    { 
+	combods.setDriverClass( driverClass ); 
+	rebind();
+    }
+	
+    public String getJdbcUrl()
+    { return combods.getJdbcUrl(); }
+	
+    public void setJdbcUrl( String jdbcUrl ) throws NamingException
+    { 
+	combods.setJdbcUrl( jdbcUrl ); 
+	rebind();
+    }
+	
+    // DriverManagerDataSource "virtual properties" based on properties
+    public String getUser()
+    { return combods.getUser(); }
+	
+    public void setUser( String user ) throws NamingException
+    { 
+	combods.setUser( user ); 
+	rebind();
+    }
+	
+    public String getPassword()
+    { return combods.getPassword(); }
+	
+    public void setPassword( String password ) throws NamingException
+    { 
+	combods.setPassword( password ); 
+	rebind();
+    }
+
+    // WrapperConnectionPoolDataSource properties (count: 21)
+    public int getCheckoutTimeout()
+    { return combods.getCheckoutTimeout(); }
+	
+    public void setCheckoutTimeout( int checkoutTimeout ) throws NamingException
+    { 
+	combods.setCheckoutTimeout( checkoutTimeout ); 
+	rebind();
+    }
+
+    public int getAcquireIncrement()
+    { return combods.getAcquireIncrement(); }
+	
+    public void setAcquireIncrement( int acquireIncrement ) throws NamingException
+    { 
+	combods.setAcquireIncrement( acquireIncrement ); 
+	rebind();
+    }
+	
+    public int getAcquireRetryAttempts()
+    { return combods.getAcquireRetryAttempts(); }
+	
+    public void setAcquireRetryAttempts( int acquireRetryAttempts ) throws NamingException
+    { 
+	combods.setAcquireRetryAttempts( acquireRetryAttempts ); 
+	rebind();
+    }
+	
+    public int getAcquireRetryDelay()
+    { return combods.getAcquireRetryDelay(); }
+	
+    public void setAcquireRetryDelay( int acquireRetryDelay ) throws NamingException
+    { 
+	combods.setAcquireRetryDelay( acquireRetryDelay ); 
+	rebind();
+    }
+	
+    public boolean isAutoCommitOnClose()
+    { return combods.isAutoCommitOnClose(); }
+
+    public void setAutoCommitOnClose( boolean autoCommitOnClose ) throws NamingException
+    { 
+	combods.setAutoCommitOnClose( autoCommitOnClose ); 
+	rebind();
+    }
+	
+    public String getConnectionTesterClassName()
+    { return combods.getConnectionTesterClassName(); }
+	
+    public void setConnectionTesterClassName( String connectionTesterClassName ) throws PropertyVetoException, NamingException
+    { 
+	combods.setConnectionTesterClassName( connectionTesterClassName ); 
+	rebind();
+    }
+	
+    public String getAutomaticTestTable()
+    { return combods.getAutomaticTestTable(); }
+	
+    public void setAutomaticTestTable( String automaticTestTable ) throws NamingException
+    { 
+	combods.setAutomaticTestTable( automaticTestTable ); 
+	rebind();
+    }
+	
+    public boolean isForceIgnoreUnresolvedTransactions()
+    { return combods.isForceIgnoreUnresolvedTransactions(); }
+	
+    public void setForceIgnoreUnresolvedTransactions( boolean forceIgnoreUnresolvedTransactions ) throws NamingException
+    { 
+	combods.setForceIgnoreUnresolvedTransactions( forceIgnoreUnresolvedTransactions ); 
+	rebind();
+    }
+	
+    public int getIdleConnectionTestPeriod()
+    { return combods.getIdleConnectionTestPeriod(); }
+	
+    public void setIdleConnectionTestPeriod( int idleConnectionTestPeriod ) throws NamingException
+    { 
+	combods.setIdleConnectionTestPeriod( idleConnectionTestPeriod ); 
+	rebind();
+    }
+    
+    public int getInitialPoolSize()
+    { return combods.getInitialPoolSize(); }
+	
+    public void setInitialPoolSize( int initialPoolSize ) throws NamingException
+    { 
+	combods.setInitialPoolSize( initialPoolSize ); 
+	rebind();
+    }
+
+    public int getMaxIdleTime()
+    { return combods.getMaxIdleTime(); }
+	
+    public void setMaxIdleTime( int maxIdleTime ) throws NamingException
+    { 
+	combods.setMaxIdleTime( maxIdleTime ); 
+	rebind();
+    }
+	
+    public int getMaxPoolSize()
+    { return combods.getMaxPoolSize(); }
+	
+    public void setMaxPoolSize( int maxPoolSize ) throws NamingException
+    { 
+	combods.setMaxPoolSize( maxPoolSize ); 
+	rebind();
+    }
+	
+    public int getMaxStatements()
+    { return combods.getMaxStatements(); }
+	
+    public void setMaxStatements( int maxStatements ) throws NamingException
+    { 
+	combods.setMaxStatements( maxStatements ); 
+	rebind();
+    }
+	
+    public int getMaxStatementsPerConnection()
+    { return combods.getMaxStatementsPerConnection(); }
+	
+    public void setMaxStatementsPerConnection( int maxStatementsPerConnection ) throws NamingException
+    { 
+	combods.setMaxStatementsPerConnection( maxStatementsPerConnection ); 
+	rebind();
+    }
+	
+    public int getMinPoolSize()
+    { return combods.getMinPoolSize(); }
+	
+    public void setMinPoolSize( int minPoolSize ) throws NamingException
+    { 
+	combods.setMinPoolSize( minPoolSize ); 
+	rebind();
+    }
+	
+    public int getPropertyCycle()
+    { return combods.getPropertyCycle(); }
+	
+    public void setPropertyCycle( int propertyCycle ) throws NamingException
+    { 
+	combods.setPropertyCycle( propertyCycle ); 
+	rebind();
+    }
+    
+    public boolean isBreakAfterAcquireFailure()
+    { return combods.isBreakAfterAcquireFailure(); }
+    
+    public void setBreakAfterAcquireFailure( boolean breakAfterAcquireFailure ) throws NamingException
+    { 
+	combods.setBreakAfterAcquireFailure( breakAfterAcquireFailure ); 
+	rebind();
+    }
+    
+    public boolean isTestConnectionOnCheckout()
+    { return combods.isTestConnectionOnCheckout(); }
+	
+    public void setTestConnectionOnCheckout( boolean testConnectionOnCheckout ) throws NamingException
+    { 
+	combods.setTestConnectionOnCheckout( testConnectionOnCheckout ); 
+	rebind();
+    }
+	
+    public boolean isTestConnectionOnCheckin()
+    { return combods.isTestConnectionOnCheckin(); }
+	
+    public void setTestConnectionOnCheckin( boolean testConnectionOnCheckin ) throws NamingException
+    { 
+	combods.setTestConnectionOnCheckin( testConnectionOnCheckin ); 
+	rebind();
+    }
+	
+    public boolean isUsesTraditionalReflectiveProxies()
+    { return combods.isUsesTraditionalReflectiveProxies(); }
+	
+    public void setUsesTraditionalReflectiveProxies( boolean usesTraditionalReflectiveProxies ) throws NamingException
+    { 
+	combods.setUsesTraditionalReflectiveProxies( usesTraditionalReflectiveProxies ); 
+	rebind();
+    }
+
+    public String getPreferredTestQuery()
+    { return combods.getPreferredTestQuery(); }
+	
+    public void setPreferredTestQuery( String preferredTestQuery ) throws NamingException
+    { 
+	combods.setPreferredTestQuery( preferredTestQuery ); 
+	rebind();
+    }
+
+    // PoolBackedDataSource properties (count: 2)
+    public String getDataSourceName()
+    { return combods.getDataSourceName(); }
+	
+    public void setDataSourceName( String name ) throws NamingException
+    { 
+	combods.setDataSourceName( name ); 
+	rebind();
+    }
+
+    public int getNumHelperThreads()
+    { return combods.getNumHelperThreads(); }
+	
+    public void setNumHelperThreads( int numHelperThreads ) throws NamingException
+    { 
+	combods.setNumHelperThreads( numHelperThreads ); 
+	rebind();
+    }
+
+    // shared properties (count: 1)
+    public String getFactoryClassLocation()
+    { return combods.getFactoryClassLocation(); }
+    
+    public void setFactoryClassLocation( String factoryClassLocation ) throws NamingException
+    { 
+	combods.setFactoryClassLocation( factoryClassLocation ); 
+	rebind();
+    }
+
+    // PooledDataSource statistics
+
+    public int getNumUserPools() throws SQLException
+    { return combods.getNumUserPools(); }
+
+    public int getNumConnectionsDefaultUser() throws SQLException
+    { return combods.getNumConnectionsDefaultUser(); }
+
+    public int getNumIdleConnectionsDefaultUser() throws SQLException
+    { return combods.getNumIdleConnectionsDefaultUser(); }
+
+    public int getNumBusyConnectionsDefaultUser() throws SQLException
+    { return combods.getNumBusyConnectionsDefaultUser(); }
+
+    public int getNumUnclosedOrphanedConnectionsDefaultUser() throws SQLException
+    { return combods.getNumUnclosedOrphanedConnectionsDefaultUser(); }
+
+    public int getNumConnections(String username, String password) throws SQLException
+    { return combods.getNumConnections(username, password); }
+
+    public int getNumIdleConnections(String username, String password) throws SQLException
+    { return combods.getNumIdleConnections(username, password); }
+
+    public int getNumBusyConnections(String username, String password) throws SQLException
+    { return combods.getNumBusyConnections(username, password); }
+
+    public int getNumUnclosedOrphanedConnections(String username, String password) throws SQLException
+    { return combods.getNumUnclosedOrphanedConnections(username, password); }
+
+    public int getNumConnectionsAllUsers() throws SQLException
+    { return combods.getNumConnectionsAllUsers(); }
+
+    public int getNumIdleConnectionsAllUsers() throws SQLException
+    { return combods.getNumIdleConnectionsAllUsers(); }
+
+    public int getNumBusyConnectionsAllUsers() throws SQLException
+    { return combods.getNumBusyConnectionsAllUsers(); }
+
+    public int getNumUnclosedOrphanedConnectionsAllUsers() throws SQLException
+    { return combods.getNumUnclosedOrphanedConnectionsAllUsers(); }
+
+    // PooledDataSource operations
+    public void softResetDefaultUser() throws SQLException
+    { combods.softResetDefaultUser(); }
+
+    public void softReset(String username, String password) throws SQLException
+    { combods.softReset(username, password); }
+
+    public void softResetAllUsers() throws SQLException
+    { combods.softResetAllUsers(); }
+
+    public void hardReset() throws SQLException
+    { combods.hardReset(); }
+
+    public void close() throws SQLException
+    { combods.close(); }
+
+    //JBoss only... (but these methods need not be called for the mbean to work)
+    public void create() throws Exception
+    { }
+
+    // the mbean works without this, but if called we start populating the pool early
+    public void start() throws Exception
+    { 
+	//System.err.println("Bound C3P0 PooledDataSource to name '" + jndiName + "'. Starting..."); 
+	logger.log(MLevel.INFO, "Bound C3P0 PooledDataSource to name ''{0}''. Starting...", jndiName); 
+	combods.getNumBusyConnectionsDefaultUser(); //just touch the datasource to start it up.
+    }
+
+
+    public void stop()
+    { }
+
+    public void destroy()
+    { }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/mbean/C3P0PooledDataSourceMBean.java b/src/classes/com/mchange/v2/c3p0/mbean/C3P0PooledDataSourceMBean.java
new file mode 100644
index 0000000..ce4ade6
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/mbean/C3P0PooledDataSourceMBean.java
@@ -0,0 +1,190 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.mbean;
+
+import com.mchange.v2.c3p0.*;
+import java.beans.PropertyVetoException;
+import java.sql.SQLException;
+import java.util.Properties;
+import javax.naming.NamingException;
+
+/**
+ * @deprecated Please use com.mchange.v2.c3p0.jboss.C3P0PooledDataSourceMBean
+ */
+public interface C3P0PooledDataSourceMBean
+{
+    // Jndi Setup 
+    public void setJndiName(String jndiName) throws NamingException;
+
+    public String getJndiName();
+
+    // DriverManagerDataSourceProperties
+    public String getDescription();
+	
+    public void setDescription( String description ) throws NamingException;
+	
+    public String getDriverClass();
+	
+    public void setDriverClass( String driverClass ) throws PropertyVetoException, NamingException;
+	
+    public String getJdbcUrl();
+	
+    public void setJdbcUrl( String jdbcUrl ) throws NamingException;
+	
+    // DriverManagerDataSource "virtual properties" based on properties
+    public String getUser();
+	
+    public void setUser( String user ) throws NamingException;
+	
+    public String getPassword();
+	
+    public void setPassword( String password ) throws NamingException;
+
+    // WrapperConnectionPoolDataSource properties 
+    public int getCheckoutTimeout();
+	
+    public void setCheckoutTimeout( int checkoutTimeout ) throws NamingException;
+	
+    public int getAcquireIncrement();
+	
+    public void setAcquireIncrement( int acquireIncrement ) throws NamingException;
+	
+    public int getAcquireRetryAttempts();
+	
+    public void setAcquireRetryAttempts( int acquireRetryAttempts ) throws NamingException;
+	
+    public int getAcquireRetryDelay();
+	
+    public void setAcquireRetryDelay( int acquireRetryDelay ) throws NamingException;
+	
+    public boolean isAutoCommitOnClose();
+
+    public void setAutoCommitOnClose( boolean autoCommitOnClose ) throws NamingException;
+	
+    public String getConnectionTesterClassName();
+	
+    public void setConnectionTesterClassName( String connectionTesterClassName ) throws PropertyVetoException, NamingException;
+	
+    public String getAutomaticTestTable();
+	
+    public void setAutomaticTestTable( String automaticTestTable ) throws NamingException;
+	
+    public boolean isForceIgnoreUnresolvedTransactions();
+	
+    public void setForceIgnoreUnresolvedTransactions( boolean forceIgnoreUnresolvedTransactions ) throws NamingException;
+	
+    public int getIdleConnectionTestPeriod();
+	
+    public void setIdleConnectionTestPeriod( int idleConnectionTestPeriod ) throws NamingException;
+    
+    public int getInitialPoolSize();
+	
+    public void setInitialPoolSize( int initialPoolSize ) throws NamingException;
+
+    public int getMaxIdleTime();
+	
+    public void setMaxIdleTime( int maxIdleTime ) throws NamingException;
+	
+    public int getMaxPoolSize();
+	
+    public void setMaxPoolSize( int maxPoolSize ) throws NamingException;
+	
+    public int getMaxStatements();
+	
+    public void setMaxStatements( int maxStatements ) throws NamingException;
+	
+    public int getMaxStatementsPerConnection();
+	
+    public void setMaxStatementsPerConnection( int maxStatementsPerConnection ) throws NamingException;
+	
+    public int getMinPoolSize();
+	
+    public void setMinPoolSize( int minPoolSize ) throws NamingException;
+	
+    public int getPropertyCycle();
+	
+    public void setPropertyCycle( int propertyCycle ) throws NamingException;
+    
+    public boolean isBreakAfterAcquireFailure();
+    
+    public void setBreakAfterAcquireFailure( boolean breakAfterAcquireFailure ) throws NamingException;
+    
+    public boolean isTestConnectionOnCheckout();
+	
+    public void setTestConnectionOnCheckout( boolean testConnectionOnCheckout ) throws NamingException;
+	
+    public boolean isTestConnectionOnCheckin();
+	
+    public void setTestConnectionOnCheckin( boolean testConnectionOnCheckin ) throws NamingException;
+	
+    public boolean isUsesTraditionalReflectiveProxies();
+	
+    public void setUsesTraditionalReflectiveProxies( boolean usesTraditionalReflectiveProxies ) throws NamingException;
+
+    public String getPreferredTestQuery();
+	
+    public void setPreferredTestQuery( String preferredTestQuery ) throws NamingException;
+
+    // PoolBackedDataSource properties (count: 2)
+    public int getNumHelperThreads();
+	
+    public void setNumHelperThreads( int numHelperThreads ) throws NamingException;
+
+    // shared properties (count: 1)
+    public String getFactoryClassLocation();
+    
+    public void setFactoryClassLocation( String factoryClassLocation ) throws NamingException;
+
+    // PooledDataSource statistics
+
+    public int getNumUserPools() throws SQLException;
+
+    public int getNumConnectionsDefaultUser() throws SQLException;
+    public int getNumIdleConnectionsDefaultUser() throws SQLException;
+    public int getNumBusyConnectionsDefaultUser() throws SQLException;
+    public int getNumUnclosedOrphanedConnectionsDefaultUser() throws SQLException;
+
+    public int getNumConnections(String username, String password) throws SQLException;
+    public int getNumIdleConnections(String username, String password) throws SQLException;
+    public int getNumBusyConnections(String username, String password) throws SQLException;
+    public int getNumUnclosedOrphanedConnections(String username, String password) throws SQLException;
+
+    public int getNumBusyConnectionsAllUsers() throws SQLException;
+    public int getNumIdleConnectionsAllUsers() throws SQLException;
+    public int getNumConnectionsAllUsers() throws SQLException;
+    public int getNumUnclosedOrphanedConnectionsAllUsers() throws SQLException;
+
+    // PooledDataSource operations
+    public void softResetDefaultUser() throws SQLException;
+    public void softReset(String username, String password) throws SQLException;
+    public void softResetAllUsers() throws SQLException;
+    public void hardReset() throws SQLException;
+    public void close() throws SQLException;
+    
+    //JBoss only... (but these methods need not be called for the mbean to work)
+    public void create() throws Exception;
+    public void start() throws Exception;
+    public void stop();
+    public void destroy();
+}
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/DoubleMaxStatementCache.java b/src/classes/com/mchange/v2/c3p0/stmt/DoubleMaxStatementCache.java
new file mode 100644
index 0000000..ebfe2a5
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/DoubleMaxStatementCache.java
@@ -0,0 +1,73 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.sql.*;
+import com.mchange.v2.async.AsynchronousRunner;
+
+public final class DoubleMaxStatementCache extends GooGooStatementCache
+{
+    //MT: protected by this' lock
+    int max_statements;
+    Deathmarch globalDeathmarch = new Deathmarch();
+
+    int max_statements_per_connection;
+    DeathmarchConnectionStatementManager dcsm;
+
+    public DoubleMaxStatementCache(AsynchronousRunner blockingTaskAsyncRunner, int max_statements, int max_statements_per_connection)
+    {
+	super( blockingTaskAsyncRunner );
+	this.max_statements = max_statements;
+	this.max_statements_per_connection = max_statements_per_connection;
+    }
+
+    //called only in parent's constructor
+    protected ConnectionStatementManager createConnectionStatementManager()
+    { return (this.dcsm = new DeathmarchConnectionStatementManager()); }
+
+    //called by parent only with this' lock
+    void addStatementToDeathmarches( Object pstmt, Connection physicalConnection )
+    {
+	globalDeathmarch.deathmarchStatement( pstmt );
+	dcsm.getDeathmarch( physicalConnection ).deathmarchStatement( pstmt ); 
+    }
+
+    void removeStatementFromDeathmarches( Object pstmt, Connection physicalConnection )
+    { 
+	globalDeathmarch.undeathmarchStatement( pstmt );
+	dcsm.getDeathmarch( physicalConnection ).undeathmarchStatement( pstmt ); 
+    }
+
+    boolean prepareAssimilateNewStatement(Connection pcon)
+    {
+	int cxn_stmt_count = dcsm.getNumStatementsForConnection( pcon );
+	if (cxn_stmt_count < max_statements_per_connection) //okay... we can cache another for the connection, but how 'bout globally?
+	    {
+		int global_size = this.countCachedStatements();
+		return (  global_size < max_statements || (global_size == max_statements && globalDeathmarch.cullNext()) );
+	    }
+	else //we can only cache if we can clear one from the Connection (which implies clearing one globally, so we needn't check max_statements)
+	    return (cxn_stmt_count == max_statements_per_connection && dcsm.getDeathmarch( pcon ).cullNext());
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/GlobalMaxOnlyStatementCache.java b/src/classes/com/mchange/v2/c3p0/stmt/GlobalMaxOnlyStatementCache.java
new file mode 100644
index 0000000..58e7d03
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/GlobalMaxOnlyStatementCache.java
@@ -0,0 +1,57 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.sql.*;
+import com.mchange.v2.async.AsynchronousRunner;
+
+public final class GlobalMaxOnlyStatementCache extends GooGooStatementCache
+{
+    //MT: protected by this' lock
+    int max_statements;
+    Deathmarch globalDeathmarch = new Deathmarch();
+
+    public GlobalMaxOnlyStatementCache(AsynchronousRunner blockingTaskAsyncRunner, int max_statements)
+    {
+	super( blockingTaskAsyncRunner );
+	this.max_statements = max_statements;
+    }
+
+    //called only in parent's constructor
+    protected ConnectionStatementManager createConnectionStatementManager()
+    { return new SimpleConnectionStatementManager(); }
+
+    //called by parent only with this' lock
+    void addStatementToDeathmarches( Object pstmt, Connection physicalConnection )
+    { globalDeathmarch.deathmarchStatement( pstmt ); }
+
+    void removeStatementFromDeathmarches( Object pstmt, Connection physicalConnection )
+    { globalDeathmarch.undeathmarchStatement( pstmt ); }
+
+    boolean prepareAssimilateNewStatement(Connection pcon)
+    {
+	int global_size = this.countCachedStatements();
+	return (  global_size < max_statements || (global_size == max_statements && globalDeathmarch.cullNext()) );
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/GooGooStatementCache.java b/src/classes/com/mchange/v2/c3p0/stmt/GooGooStatementCache.java
new file mode 100644
index 0000000..b71bd66
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/GooGooStatementCache.java
@@ -0,0 +1,801 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.util.*;
+import java.sql.*;
+import java.lang.reflect.*;
+import com.mchange.v2.async.AsynchronousRunner;
+import com.mchange.v2.sql.SqlUtils;
+import com.mchange.v2.util.ResourceClosedException;
+import com.mchange.v2.log.*;
+import com.mchange.v1.db.sql.StatementUtils;
+
+import java.io.StringWriter;
+import java.io.PrintWriter;
+import java.io.IOException;
+import com.mchange.v2.io.IndentedWriter;
+
+public abstract class GooGooStatementCache
+{
+    private final static MLogger logger = MLog.getLogger( GooGooStatementCache.class );
+
+    private final static int DESTROY_NEVER          = 0;
+    private final static int DESTROY_IF_CHECKED_IN  = 1 << 0; 
+    private final static int DESTROY_IF_CHECKED_OUT = 1 << 1;
+    private final static int DESTROY_ALWAYS         = (DESTROY_IF_CHECKED_IN | DESTROY_IF_CHECKED_OUT);
+
+    /* MT: protected by this's lock */
+
+    // contains all statements in the cache, 
+    // organized by connection
+    ConnectionStatementManager cxnStmtMgr;
+
+    // contains all statements in the cache, 
+    // bound to the keys that produced them
+    HashMap stmtToKey      = new HashMap();
+
+    // maps all known keys to their set of statements
+    // and to a queue of statements, if any, available
+    // for checkout
+    HashMap keyToKeyRec    = new HashMap();
+
+    // contains all checked out statements -- in the cache, 
+    // but not currently available for checkout, nor for
+    // culling in case of overflow
+    HashSet checkedOut = new HashSet();
+
+    /* MT: end protected by this' lock */
+
+    /* MT: protected by its own lock */
+
+    AsynchronousRunner blockingTaskAsyncRunner;
+
+    // This set is used to ensure that multiple threads
+    // do not try to remove the same statement from the
+    // cache, if for example a Statement is both deathmarched
+    // away and its parent Connection is closed.
+    //
+    // ALL ACCESS SHOULD BE EXPLICITLY SYNCHRONIZED
+    // ON removalPending's lock!
+    HashSet removalPending = new HashSet();
+
+    /* MT: end protected by its own lock */
+
+    public GooGooStatementCache(AsynchronousRunner blockingTaskAsyncRunner)
+    { 
+        this.blockingTaskAsyncRunner = blockingTaskAsyncRunner; 
+        this.cxnStmtMgr = createConnectionStatementManager();
+    }
+
+    public synchronized int getNumStatements()
+    { return this.isClosed() ? -1 : countCachedStatements(); }
+
+    public synchronized int getNumStatementsCheckedOut()
+    { return this.isClosed() ? -1 : checkedOut.size(); }
+
+    public synchronized int getNumConnectionsWithCachedStatements()
+    { return isClosed() ? -1 : cxnStmtMgr.getNumConnectionsWithCachedStatements(); }
+
+    public synchronized String dumpStatementCacheStatus()
+    {
+        if (isClosed())
+            return this + "status: Closed.";
+        else
+        {
+            StringWriter sw = new StringWriter(2048);
+            IndentedWriter iw = new IndentedWriter( sw );
+            try
+            {
+                iw.print(this);
+                iw.println(" status:");
+                iw.upIndent();
+                iw.println("core stats:");
+                iw.upIndent();
+                iw.print("num cached statements: ");
+                iw.println( this.countCachedStatements() );
+                iw.print("num cached statements in use: ");
+                iw.println( checkedOut.size() );
+                iw.print("num connections with cached statements: ");
+                iw.println(cxnStmtMgr.getNumConnectionsWithCachedStatements());
+                iw.downIndent();
+                iw.println("cached statement dump:");
+                iw.upIndent();
+                for (Iterator ii = cxnStmtMgr.connectionSet().iterator(); ii.hasNext();)
+                {
+                    Connection pcon = (Connection) ii.next();
+                    iw.print(pcon);
+                    iw.println(':');
+                    iw.upIndent();
+                    for (Iterator jj = cxnStmtMgr.statementSet(pcon).iterator(); jj.hasNext();)
+                        iw.println(jj.next());
+                    iw.downIndent();
+                }
+
+                iw.downIndent();
+                iw.downIndent();
+                return sw.toString();
+            }
+            catch (IOException e)
+            {
+                if (logger.isLoggable(MLevel.SEVERE))
+                    logger.log(MLevel.SEVERE, "Huh? We've seen an IOException writing to s StringWriter?!", e);
+                return e.toString();
+            }
+        }
+    }
+
+    abstract ConnectionStatementManager createConnectionStatementManager();
+
+    public synchronized Object checkoutStatement( Connection physicalConnection,
+                    Method stmtProducingMethod, 
+                    Object[] args )  
+    throws SQLException, ResourceClosedException
+    {
+        try
+        {
+            Object out = null;
+
+            StatementCacheKey key = StatementCacheKey.find( physicalConnection, 
+                            stmtProducingMethod, 
+                            args );
+            LinkedList l = checkoutQueue( key );
+            if (l == null || l.isEmpty()) //we need a new statement
+            {
+                // we might wait() here... 
+                // don't presume atomicity before and after!
+                out = acquireStatement( physicalConnection, stmtProducingMethod, args );
+
+                if ( prepareAssimilateNewStatement( physicalConnection ) )
+                    assimilateNewCheckedOutStatement( key, physicalConnection, out );
+                // else case: we can't assimilate the statement...
+                // so, we just return our newly created statement, without caching it.
+                // on check-in, it will simply be destroyed... this is an "overload statement"
+            }
+            else //okay, we can use an old one
+            {
+                if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+                    logger.finest(this.getClass().getName() + " ----> CACHE HIT");
+                //System.err.println("-------------> CACHE HIT!");
+
+                out = l.get(0);
+                l.remove(0);
+                if (! checkedOut.add( out ))
+                    throw new RuntimeException("Internal inconsistency: " +
+                                    "Checking out a statement marked " + 
+                    "as already checked out!");
+                removeStatementFromDeathmarches( out, physicalConnection );
+            }
+
+            if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+            {
+                //System.err.print("checkoutStatement(): ");
+                //printStats();
+                if (logger.isLoggable(MLevel.FINEST))
+                    logger.finest("checkoutStatement: " + statsString());
+            }
+
+            return out;
+        }
+        catch (NullPointerException npe)
+        {
+            if (checkedOut == null) //we're closed
+            {
+                if (logger.isLoggable(MLevel.FINE))
+                    logger.log( MLevel.FINE, 
+                                "A client attempted to work with a closed Statement cache, " + "" +
+                                "provoking a NullPointerException. c3p0 recovers, but this should be rare.", 
+                                npe);
+                throw new ResourceClosedException( npe );
+            }
+            else
+                throw npe;
+        }
+    }
+
+    public synchronized void checkinStatement( Object pstmt )
+    throws SQLException
+    {
+        if (checkedOut == null) //we're closed
+        {
+            synchronousDestroyStatement( pstmt );
+
+            return;
+        }
+        else if (! checkedOut.remove( pstmt ) )
+        {
+            if (! ourResource( pstmt ) ) //this is not our resource, or it is an overload statement
+                destroyStatement( pstmt ); // so we just destroy
+            //in the else case, it's already checked-in, so we ignore
+
+            return;
+        }
+
+        try
+        { refreshStatement( (PreparedStatement) pstmt ); }
+        catch (Exception e)
+        {
+            if (Debug.DEBUG)
+            {
+//              System.err.println("Problem with checked-in Statement, discarding.");
+//              e.printStackTrace();
+                if (logger.isLoggable(MLevel.INFO))
+                    logger.log(MLevel.INFO, "Problem with checked-in Statement, discarding.", e);
+            }
+
+            // swaldman -- 2004-01-31: readd problem statement to checkedOut for consistency
+            // the statement is not yet checked-in, but it is removed from checked out, and this
+            // violates the consistency assumption of removeStatement(). Thanks to Zach Scott for
+            // calling attention to this issue.
+            checkedOut.add( pstmt );
+
+            removeStatement( pstmt, DESTROY_ALWAYS ); //force destruction of the statement even though it appears checked-out
+            return;
+        }
+
+        StatementCacheKey key = (StatementCacheKey) stmtToKey.get( pstmt );
+        if (Debug.DEBUG && key == null)
+            throw new RuntimeException("Internal inconsistency: " +
+            "A checked-out statement has no key associated with it!");
+
+        LinkedList l = checkoutQueue( key );
+        l.add( pstmt );
+        addStatementToDeathmarches( pstmt, key.physicalConnection );
+
+        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+        {
+//          System.err.print("checkinStatement(): ");
+//          printStats();
+            if (logger.isLoggable(MLevel.FINEST))
+                logger.finest("checkinStatement(): " + statsString());
+        }
+    }
+
+
+    public synchronized void checkinAll(Connection pcon)
+    throws SQLException
+    {
+        //new Exception("checkinAll()").printStackTrace();
+
+        Set stmtSet = cxnStmtMgr.statementSet( pcon );
+        if (stmtSet != null)
+        {
+            for (Iterator ii = stmtSet.iterator(); ii.hasNext(); )
+            {
+                Object stmt = ii.next();
+                if (checkedOut.contains( stmt ))
+                    checkinStatement( stmt );
+            }
+        }
+
+        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+        {
+//          System.err.print("checkinAll(): ");
+//          printStats();
+            if (logger.isLoggable(MLevel.FINEST))
+                logger.log(MLevel.FINEST, "checkinAll(): " + statsString());
+        }
+    }
+
+    /*
+     * we only selectively sync' parts of this method, because we wish to wait for
+     * Statements we wish to destroy the Statements synchronously, but without
+     * holding the pool's lock.
+     */
+    public void closeAll(Connection pcon) throws SQLException
+    {
+//      System.err.println( this + ": closeAll( " + pcon + " )" );
+//      new Exception("closeAll()").printStackTrace();
+
+//      assert !Thread.holdsLock( this );
+
+        if (! this.isClosed())
+        {
+            if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+            {
+                if (logger.isLoggable(MLevel.FINEST))
+                {
+                    logger.log(MLevel.FINEST, "ENTER METHOD: closeAll( " + pcon + " )! -- num_connections: " + 
+                                    cxnStmtMgr.getNumConnectionsWithCachedStatements());
+                    //logger.log(MLevel.FINEST, "Set of statements for connection: " + cSet + (cSet != null ? "; size: " + cSet.size() : ""));
+                }
+            }
+
+            Set stmtSet = null;
+            synchronized (this)
+            {
+                Set cSet = cxnStmtMgr.statementSet( pcon ); 
+
+                if (cSet != null)
+                {
+                    //the removeStatement(...) removes from cSet, so we can't be iterating over cSet directly
+                    stmtSet = new HashSet( cSet );
+                    //System.err.println("SIZE FOR CONNECTION SET: " + stmtSet.size());
+
+                    for (Iterator ii = stmtSet.iterator(); ii.hasNext(); )
+                    {
+                        Object stmt = ii.next();
+                        // we remove without destroying, leaving the destruction
+                        // until when we lose the pool's lock
+                        removeStatement( stmt, DESTROY_NEVER ); 
+                    }
+                }
+            }
+
+            if ( stmtSet != null )
+            {
+                for (Iterator ii = stmtSet.iterator(); ii.hasNext(); )
+                {
+                    Object stmt = ii.next();
+                    synchronousDestroyStatement( stmt );
+                }
+            }
+
+            if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+            {
+                if (logger.isLoggable(MLevel.FINEST))
+                    logger.finest("closeAll(): " + statsString());
+            }
+        }
+//      else
+//      {
+//      if (logger.isLoggable(MLevel.FINER))
+//      logger.log(MLevel.FINER, 
+//      this + ":  call to closeAll() when statment cache is already closed! [not harmful! debug only!]", 
+//      new Exception("DUPLICATE CLOSE DEBUG STACK TRACE."));
+//      }
+    }
+
+    public synchronized void close() 
+    throws SQLException
+    {
+        //System.err.println( this + ": close()" );
+
+        if (! isClosed())
+        {
+            for (Iterator ii = stmtToKey.keySet().iterator(); ii.hasNext(); )
+                synchronousDestroyStatement( ii.next() );
+
+            cxnStmtMgr       = null;
+            stmtToKey        = null;
+            keyToKeyRec      = null;
+            checkedOut       = null;
+        }
+        else
+        {
+            if (logger.isLoggable(MLevel.FINE))
+                logger.log(MLevel.FINE, this + ": duplicate call to close() [not harmful! -- debug only!]", new Exception("DUPLICATE CLOSE DEBUG STACK TRACE."));
+        }
+
+    }
+
+
+    public synchronized boolean isClosed()
+    { return cxnStmtMgr == null; }
+
+    /* non-public methods that needn't be called with this' lock below */
+
+    private void destroyStatement( final Object pstmt )
+    {
+        class StatementCloseTask implements Runnable
+        {
+            public void run()
+            { StatementUtils.attemptClose( (PreparedStatement) pstmt ); }
+        }
+
+        Runnable r = new StatementCloseTask();
+
+        blockingTaskAsyncRunner.postRunnable(r);
+    }
+
+    private void synchronousDestroyStatement( final Object pstmt )
+    { StatementUtils.attemptClose( (PreparedStatement) pstmt ); }
+
+    /* end non-public methods that needn't be called with this' lock */
+
+
+
+    /* non-public methods that MUST be called with this' lock */
+
+    abstract boolean prepareAssimilateNewStatement(Connection pcon);
+
+    abstract void addStatementToDeathmarches( Object pstmt, Connection physicalConnection );
+    abstract void removeStatementFromDeathmarches( Object pstmt, Connection physicalConnection );
+
+    final int countCachedStatements()
+    { return stmtToKey.size(); }
+
+    private void assimilateNewCheckedOutStatement( StatementCacheKey key, 
+                    Connection pConn, 
+                    Object ps )
+    {
+        stmtToKey.put( ps, key );
+        HashSet ks = keySet( key );
+        if (ks == null)
+            keyToKeyRec.put( key, new KeyRec() );
+        else 
+        {
+            //System.err.println("-------> Multiply prepared statement! " + key.stmtText );
+            if (logger.isLoggable(MLevel.INFO))
+                logger.info("Multiply prepared statement! " + key.stmtText );
+            if (Debug.DEBUG && logger.isLoggable(MLevel.FINE))
+                logger.fine("(The same statement has already been prepared by this Connection, " +
+                                "and that other instance has not yet been closed, so the statement pool " +
+                                "has to prepare a second PreparedStatement object rather than reusing " +
+                                "the previously-cached Statement. The new Statement will be cached, in case " +
+                "you frequently need multiple copies of this Statement.)");
+        }
+        keySet( key ).add( ps );
+        cxnStmtMgr.addStatementForConnection( ps, pConn );
+
+        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+        {
+//          System.err.println("cxnStmtMgr.statementSet( " + pConn + " ).size(): " + 
+//          cxnStmtMgr.statementSet( pConn ).size());
+            if (logger.isLoggable(MLevel.FINEST))
+                logger.finest("cxnStmtMgr.statementSet( " + pConn + " ).size(): " + 
+                                cxnStmtMgr.statementSet( pConn ).size());
+        }
+
+        checkedOut.add( ps );
+    }
+
+    private void removeStatement( Object ps , int destruction_policy )
+    {
+        synchronized (removalPending)
+        {
+            if ( removalPending.contains( ps ) )
+                return;
+            else
+                removalPending.add(ps);
+        }
+
+        StatementCacheKey sck = (StatementCacheKey) stmtToKey.remove( ps );
+        removeFromKeySet( sck, ps );
+        Connection pConn = sck.physicalConnection;
+
+        boolean checked_in = !checkedOut.contains( ps );
+
+        if ( checked_in )
+        {
+            removeStatementFromDeathmarches( ps, pConn );
+            removeFromCheckoutQueue( sck , ps );
+            if ((destruction_policy & DESTROY_IF_CHECKED_IN) != 0)
+                destroyStatement( ps );
+        }
+        else
+        {
+            checkedOut.remove( ps );
+            if ((destruction_policy & DESTROY_IF_CHECKED_OUT) != 0)
+                destroyStatement( ps );
+        }
+
+
+        boolean check =	cxnStmtMgr.removeStatementForConnection( ps, pConn );
+        if (Debug.DEBUG && check == false)
+        {
+            //new Exception("WARNING: removed a statement that apparently wasn't in a statement set!!!").printStackTrace();
+            if (logger.isLoggable(MLevel.WARNING))
+                logger.log(MLevel.WARNING, 
+                                this + " removed a statement that apparently wasn't in a statement set!!!",
+                                new Exception("LOG STACK TRACE"));
+        }
+
+        synchronized (removalPending)
+        { removalPending.remove(ps); }
+    }
+
+    private Object acquireStatement(final Connection pConn, 
+                    final Method stmtProducingMethod, 
+                    final Object[] args )
+    throws SQLException
+    {
+        try
+        {
+            final Object[] outHolder = new Object[1];
+            final SQLException[] exceptionHolder = new SQLException[1];
+
+            class StmtAcquireTask implements Runnable
+            {
+                public void run()
+                {
+                    try
+                    {
+                        outHolder[0] = 
+                            stmtProducingMethod.invoke( pConn, 
+                                            args ); 
+                    }
+                    catch ( InvocationTargetException e )
+                    { 
+                        Throwable targetException = e.getTargetException();
+                        if ( targetException instanceof SQLException )
+                            exceptionHolder[0] = (SQLException) targetException;
+                        else
+                            exceptionHolder[0] 
+                                            = SqlUtils.toSQLException(targetException);
+                    }
+                    catch ( Exception e )
+                    { exceptionHolder[0] = SqlUtils.toSQLException(e); }
+                    finally
+                    { 
+                        synchronized ( GooGooStatementCache.this )
+                        { GooGooStatementCache.this.notifyAll(); }
+                    }
+                }
+            }
+
+            Runnable r = new StmtAcquireTask();
+            blockingTaskAsyncRunner.postRunnable(r);
+
+            while ( outHolder[0] == null && exceptionHolder[0] == null )
+                this.wait(); //give up our lock while the Statement gets prepared
+            if (exceptionHolder[0] != null)
+                throw exceptionHolder[0];
+            else
+            {
+                Object out = outHolder[0];
+                return out;
+            }
+        }
+        catch ( InterruptedException e )
+        { throw SqlUtils.toSQLException( e ); }
+    }
+
+    private KeyRec keyRec( StatementCacheKey key )
+    { return ((KeyRec) keyToKeyRec.get( key )); }
+
+    private HashSet keySet( StatementCacheKey key )
+    { 
+        KeyRec rec = keyRec( key );
+        return (rec == null ? null : rec.allStmts); 
+    }
+
+    private boolean removeFromKeySet( StatementCacheKey key, Object pstmt )
+    {
+        boolean out;
+        HashSet stmtSet = keySet( key );
+        out = stmtSet.remove( pstmt );
+        if (stmtSet.isEmpty() && checkoutQueue( key ).isEmpty())
+            keyToKeyRec.remove( key );
+        return out;
+    }
+
+    private LinkedList checkoutQueue( StatementCacheKey key )
+    { 
+        KeyRec rec = keyRec( key );
+        return ( rec == null ? null : rec.checkoutQueue );
+    }
+
+    private boolean removeFromCheckoutQueue( StatementCacheKey key, Object pstmt )
+    {
+        boolean out;
+        LinkedList q = checkoutQueue( key );
+        out = q.remove( pstmt );
+        if (q.isEmpty() && keySet( key ).isEmpty())
+            keyToKeyRec.remove( key );
+        return out;
+    }
+
+    private boolean ourResource( Object ps )
+    { return stmtToKey.keySet().contains( ps ); }
+
+    private void refreshStatement( PreparedStatement ps ) throws Exception
+    { ps.clearParameters(); }
+
+    private void printStats()
+    {
+        //new Exception("printStats()").printStackTrace();
+        int total_size = this.countCachedStatements();
+        int checked_out_size = checkedOut.size();
+        int num_connections  = cxnStmtMgr.getNumConnectionsWithCachedStatements(); 
+        int num_keys = keyToKeyRec.size(); 
+        System.err.print(this.getClass().getName() + " stats -- ");
+        System.err.print("total size: " + total_size);
+        System.err.print("; checked out: " + checked_out_size);
+        System.err.print("; num connections: " + num_connections);
+        System.err.println("; num keys: " + num_keys);
+    }
+
+    private String statsString()
+    {
+        int total_size = this.countCachedStatements();
+        int checked_out_size = checkedOut.size();
+        int num_connections  = cxnStmtMgr.getNumConnectionsWithCachedStatements(); 
+        int num_keys = keyToKeyRec.size(); 
+
+        StringBuffer sb = new StringBuffer(255);
+        sb.append(this.getClass().getName());
+        sb.append(" stats -- ");
+        sb.append("total size: ");
+        sb.append(total_size);
+        sb.append("; checked out: ");
+        sb.append(checked_out_size);
+        sb.append("; num connections: ");
+        sb.append(num_connections);
+        sb.append("; num keys: ");
+        sb.append(num_keys);
+        return sb.toString();
+    }
+
+
+    private static class KeyRec
+    {
+        HashSet  allStmts       = new HashSet();
+        LinkedList checkoutQueue  = new LinkedList();
+    }
+
+    protected class Deathmarch
+    {
+        TreeMap longsToStmts = new TreeMap(); 
+        HashMap stmtsToLongs = new HashMap();
+
+        long last_long = -1;
+
+        public void deathmarchStatement( Object ps )
+        {
+            //System.err.println("deathmarchStatement( " + ps + " )");
+            if (Debug.DEBUG)
+            {
+                Long old = (Long) stmtsToLongs.get( ps );
+                if (old != null)
+                    throw new RuntimeException("Internal inconsistency: " +
+                                    "A statement is being double-deathmatched. no checked-out statements should be in a deathmarch already; " +
+                    "no already checked-in statement should be deathmarched!");
+            }
+
+            Long youth = getNextLong();
+            stmtsToLongs.put( ps, youth );
+            longsToStmts.put( youth, ps );
+        }
+
+        public void undeathmarchStatement( Object ps )
+        {
+            Long old = (Long) stmtsToLongs.remove( ps );
+            if (Debug.DEBUG && old == null)
+                throw new RuntimeException("Internal inconsistency: " +
+                "A (not new) checking-out statement is not in deathmarch.");
+            Object check = longsToStmts.remove( old );
+            if (Debug.DEBUG && old == null)
+                throw new RuntimeException("Internal inconsistency: " +
+                "A (not new) checking-out statement is not in deathmarch.");
+        }
+
+        public boolean cullNext()
+        {
+            if ( longsToStmts.isEmpty() )
+                return false;
+            else
+            {
+                Long l = (Long) longsToStmts.firstKey();
+                Object ps = longsToStmts.get( l );
+                if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+                {
+//                  System.err.println("CULLING: " + 
+//                  ((StatementCacheKey) stmtToKey.get(ps)).stmtText);
+                    if (logger.isLoggable(MLevel.FINEST))
+                        logger.finest("CULLING: " + ((StatementCacheKey) stmtToKey.get(ps)).stmtText);
+                }
+                // we do not undeathmarch the statement ourselves, because removeStatement( ... )
+                // should remove from all deathmarches...
+                removeStatement( ps, DESTROY_ALWAYS ); 
+                if (Debug.DEBUG && this.contains( ps ))
+                    throw new RuntimeException("Inconsistency!!! Statement culled from deathmarch failed to be removed by removeStatement( ... )!");
+                return true;
+            }
+        }
+
+        public boolean contains( Object ps )
+        { return stmtsToLongs.keySet().contains( ps ); }
+
+        public int size()
+        { return longsToStmts.size(); }
+
+        private Long getNextLong()
+        { return new Long( ++last_long ); }
+    }
+
+    protected static abstract class ConnectionStatementManager
+    {
+        Map cxnToStmtSets = new HashMap();
+
+        public int getNumConnectionsWithCachedStatements()
+        { return cxnToStmtSets.size(); }
+
+        public Set connectionSet()
+        { return cxnToStmtSets.keySet(); }
+
+        public Set statementSet( Connection pcon )
+        { return (Set) cxnToStmtSets.get( pcon ); }
+
+        public int getNumStatementsForConnection( Connection pcon )
+        {
+            Set stmtSet = statementSet( pcon );
+            return (stmtSet == null ? 0 : stmtSet.size());
+        }
+
+        public void addStatementForConnection( Object ps, Connection pcon )
+        {
+            Set stmtSet = statementSet( pcon );
+            if (stmtSet == null)
+            {
+                stmtSet = new HashSet();
+                cxnToStmtSets.put( pcon, stmtSet );
+            }
+            stmtSet.add( ps );
+        }
+
+        public boolean removeStatementForConnection( Object ps, Connection pcon )
+        {
+            boolean out;
+
+            Set stmtSet = statementSet( pcon );
+            if ( stmtSet != null )
+            {
+                out = stmtSet.remove( ps );
+                if (stmtSet.isEmpty())
+                    cxnToStmtSets.remove( pcon );
+            }
+            else
+                out = false;
+
+            return out;
+        }
+    }
+
+    // i want this as optimized as possible, so i'm adopting the philosophy that all
+    // classes are abstract or final, to help enable compiler inlining...
+    protected static final class SimpleConnectionStatementManager extends ConnectionStatementManager 
+    {}
+
+    protected final class DeathmarchConnectionStatementManager extends ConnectionStatementManager
+    {
+        Map cxnsToDms = new HashMap();
+
+        public void addStatementForConnection( Object ps, Connection pcon )
+        {
+            super.addStatementForConnection( ps, pcon );
+            Deathmarch dm = (Deathmarch) cxnsToDms.get( pcon );
+            if (dm == null)
+            {
+                dm = new Deathmarch();
+                cxnsToDms.put( pcon, dm );
+            }
+        }
+
+        public boolean removeStatementForConnection( Object ps, Connection pcon )
+        {
+            boolean out = super.removeStatementForConnection( ps, pcon );
+            if (out)
+            {
+                if ( statementSet( pcon ) == null )
+                    cxnsToDms.remove( pcon );
+            }
+            return out;
+        }
+
+        public Deathmarch getDeathmarch( Connection pcon )
+        { return (Deathmarch) cxnsToDms.get( pcon ); }
+    }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/MemoryCoalescedStatementCacheKey.java b/src/classes/com/mchange/v2/c3p0/stmt/MemoryCoalescedStatementCacheKey.java
new file mode 100644
index 0000000..8e4f67a
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/MemoryCoalescedStatementCacheKey.java
@@ -0,0 +1,165 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.lang.reflect.Method;
+import com.mchange.v2.coalesce.*;
+
+final class MemoryCoalescedStatementCacheKey extends StatementCacheKey
+{
+    //MT: not thread-safe, but protected within the find() method
+    //    by StatementCacheKey.class lock
+    final static Coalescer keyCoalescer = CoalescerFactory.createCoalescer( true, false );
+
+    static StatementCacheKey _find( Connection pcon, Method stmtProducingMethod, Object[] args )
+    {
+	///BEGIN FIND LOGIC///
+	String stmtText = (String) args[0];
+	boolean is_callable = stmtProducingMethod.getName().equals("prepareCall");
+	int result_set_type;
+	int result_set_concurrency;
+
+	int[] columnIndexes;
+	String[] columnNames;
+	Integer autogeneratedKeys;
+	Integer resultSetHoldability;
+
+	if (args.length == 1)
+	    {
+		result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+		result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+		columnIndexes          = null;
+		columnNames            = null;
+		autogeneratedKeys      = null;
+		resultSetHoldability   = null;
+	    }
+	else if (args.length == 2)
+	    {
+		Class[] argTypes = stmtProducingMethod.getParameterTypes();
+		if (argTypes[1].isArray())
+		    {
+			Class baseType = argTypes[1].getComponentType();
+			if (baseType == int.class) //second arg is columnIndexes
+			    {
+				result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+				result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+				columnIndexes          = (int[]) args[1];
+				columnNames            = null;
+				autogeneratedKeys      = null;
+				resultSetHoldability   = null;
+			    }
+			else if (baseType == String.class)
+			    {
+				result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+				result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+				columnIndexes          = null;
+				columnNames            = (String[]) args[1];
+				autogeneratedKeys      = null;
+				resultSetHoldability   = null;
+			    }
+			else
+			    throw new IllegalArgumentException("c3p0 probably needs to be updated for some new " +
+							       "JDBC spec! As of JDBC3, we expect two arg statement " +
+							       "producing methods where the second arg is either " +
+							       "an int, int array, or String array.");
+		    }
+		else //it should be a boxed int, autogeneratedKeys
+		    {
+			result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+			result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+			columnIndexes          = null;
+			columnNames            = null;
+			autogeneratedKeys      = (Integer) args[1];
+			resultSetHoldability   = null;
+		    }
+	    }
+	else if (args.length == 3)
+	    {
+		result_set_type        = ((Integer) args[1]).intValue();
+		result_set_concurrency = ((Integer) args[2]).intValue();
+		columnIndexes          = null;
+		columnNames            = null;
+		autogeneratedKeys      = null;
+		resultSetHoldability   = null;
+	    }
+	else if (args.length == 4)
+	    {
+		result_set_type        = ((Integer) args[1]).intValue();
+		result_set_concurrency = ((Integer) args[2]).intValue();
+		columnIndexes          = null;
+		columnNames            = null;
+		autogeneratedKeys      = null;
+		resultSetHoldability   = (Integer) args[3];
+	    }
+	else
+	    throw new IllegalArgumentException("Unexpected number of args to " + 
+					       stmtProducingMethod.getName() );
+	///END FIND LOGIC///
+
+
+	StatementCacheKey uncanonical 
+	    =  new MemoryCoalescedStatementCacheKey( pcon, 
+						     stmtText, 
+						     is_callable, 
+						     result_set_type, 
+						     result_set_concurrency,
+						     columnIndexes,
+						     columnNames,
+						     autogeneratedKeys,
+						     resultSetHoldability );
+	return (StatementCacheKey) keyCoalescer.coalesce( uncanonical );
+    }
+
+    MemoryCoalescedStatementCacheKey( Connection physicalConnection,
+				      String stmtText,
+				      boolean is_callable,
+				      int result_set_type,
+				      int result_set_concurrency,
+				      int[] columnIndexes,
+				      String[] columnNames,
+				      Integer autogeneratedKeys,
+				      Integer resultSetHoldability )
+    {
+	super( physicalConnection,
+	       stmtText,
+	       is_callable,
+	       result_set_type,
+	       result_set_concurrency,
+	       columnIndexes,
+	       columnNames,
+	       autogeneratedKeys,
+	       resultSetHoldability );
+    }
+
+    public boolean equals( Object o )
+    { return StatementCacheKey.equals( this, o ); }
+
+    public int hashCode()
+    { return StatementCacheKey.hashCode( this ); }
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/PerConnectionMaxOnlyStatementCache.java b/src/classes/com/mchange/v2/c3p0/stmt/PerConnectionMaxOnlyStatementCache.java
new file mode 100644
index 0000000..8ec2d65
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/PerConnectionMaxOnlyStatementCache.java
@@ -0,0 +1,57 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.sql.*;
+import com.mchange.v2.async.AsynchronousRunner;
+
+public final class PerConnectionMaxOnlyStatementCache extends GooGooStatementCache
+{
+    //MT: protected by this' lock
+    int max_statements_per_connection;
+    DeathmarchConnectionStatementManager dcsm;
+
+    public PerConnectionMaxOnlyStatementCache(AsynchronousRunner blockingTaskAsyncRunner, int max_statements_per_connection)
+    {
+	super( blockingTaskAsyncRunner );
+	this.max_statements_per_connection = max_statements_per_connection;
+    }
+
+    //called only in parent's constructor
+    protected ConnectionStatementManager createConnectionStatementManager()
+    { return (this.dcsm = new DeathmarchConnectionStatementManager()); }
+
+    //called by parent only with this' lock
+    void addStatementToDeathmarches( Object pstmt, Connection physicalConnection )
+    { dcsm.getDeathmarch( physicalConnection ).deathmarchStatement( pstmt ); }
+
+    void removeStatementFromDeathmarches( Object pstmt, Connection physicalConnection )
+    { dcsm.getDeathmarch( physicalConnection ).undeathmarchStatement( pstmt ); }
+
+    boolean prepareAssimilateNewStatement(Connection pcon)
+    {
+	int cxn_stmt_count = dcsm.getNumStatementsForConnection( pcon );
+	return ( cxn_stmt_count < max_statements_per_connection || (cxn_stmt_count == max_statements_per_connection && dcsm.getDeathmarch( pcon ).cullNext()) );
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/SimpleStatementCacheKey.java b/src/classes/com/mchange/v2/c3p0/stmt/SimpleStatementCacheKey.java
new file mode 100644
index 0000000..b569a89
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/SimpleStatementCacheKey.java
@@ -0,0 +1,155 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.lang.reflect.Method;
+
+final class SimpleStatementCacheKey extends StatementCacheKey
+{
+    static StatementCacheKey _find( Connection pcon, Method stmtProducingMethod, Object[] args )
+    {
+	///BEGIN FIND LOGIC///
+	String stmtText = (String) args[0];
+	boolean is_callable = stmtProducingMethod.getName().equals("prepareCall");
+	int result_set_type;
+	int result_set_concurrency;
+
+	int[] columnIndexes;
+	String[] columnNames;
+	Integer autogeneratedKeys;
+	Integer resultSetHoldability;
+
+	if (args.length == 1)
+	    {
+		result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+		result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+		columnIndexes          = null;
+		columnNames            = null;
+		autogeneratedKeys      = null;
+		resultSetHoldability   = null;
+	    }
+	else if (args.length == 2)
+	    {
+		Class[] argTypes = stmtProducingMethod.getParameterTypes();
+		if (argTypes[1].isArray())
+		    {
+			Class baseType = argTypes[1].getComponentType();
+			if (baseType == int.class) //second arg is columnIndexes
+			    {
+				result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+				result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+				columnIndexes          = (int[]) args[1];
+				columnNames            = null;
+				autogeneratedKeys      = null;
+				resultSetHoldability   = null;
+			    }
+			else if (baseType == String.class)
+			    {
+				result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+				result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+				columnIndexes          = null;
+				columnNames            = (String[]) args[1];
+				autogeneratedKeys      = null;
+				resultSetHoldability   = null;
+			    }
+			else
+			    throw new IllegalArgumentException("c3p0 probably needs to be updated for some new " +
+							       "JDBC spec! As of JDBC3, we expect two arg statement " +
+							       "producing methods where the second arg is either " +
+							       "an int, int array, or String array.");
+		    }
+		else //it should be a boxed int, autogeneratedKeys
+		    {
+			result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+			result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+			columnIndexes          = null;
+			columnNames            = null;
+			autogeneratedKeys      = (Integer) args[1];
+			resultSetHoldability   = null;
+		    }
+	    }
+	else if (args.length == 3)
+	    {
+		result_set_type        = ((Integer) args[1]).intValue();
+		result_set_concurrency = ((Integer) args[2]).intValue();
+		columnIndexes          = null;
+		columnNames            = null;
+		autogeneratedKeys      = null;
+		resultSetHoldability   = null;
+	    }
+	else if (args.length == 4)
+	    {
+		result_set_type        = ((Integer) args[1]).intValue();
+		result_set_concurrency = ((Integer) args[2]).intValue();
+		columnIndexes          = null;
+		columnNames            = null;
+		autogeneratedKeys      = null;
+		resultSetHoldability   = (Integer) args[3];
+	    }
+	else
+	    throw new IllegalArgumentException("Unexpected number of args to " + 
+					       stmtProducingMethod.getName() );
+	///END FIND LOGIC///
+
+
+	return new SimpleStatementCacheKey( pcon, 
+					    stmtText, 
+					    is_callable, 
+					    result_set_type, 
+					    result_set_concurrency,
+					    columnIndexes,
+					    columnNames,
+					    autogeneratedKeys,
+					    resultSetHoldability );
+    }
+
+    SimpleStatementCacheKey( Connection physicalConnection,
+			     String stmtText,
+			     boolean is_callable,
+			     int result_set_type,
+			     int result_set_concurrency,
+			     int[] columnIndexes,
+			     String[] columnNames,
+			     Integer autogeneratedKeys,
+			     Integer resultSetHoldability )
+    {
+	super( physicalConnection,
+	       stmtText,
+	       is_callable,
+	       result_set_type,
+	       result_set_concurrency,
+	       columnIndexes,
+	       columnNames,
+	       autogeneratedKeys,
+	       resultSetHoldability );
+    }
+
+    public boolean equals( Object o )
+    { return StatementCacheKey.equals( this, o ); }
+
+    public int hashCode()
+    { return StatementCacheKey.hashCode( this ); }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/StatementCache.java b/src/classes/com/mchange/v2/c3p0/stmt/StatementCache.java
new file mode 100644
index 0000000..fcdf82a
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/StatementCache.java
@@ -0,0 +1,49 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.lang.reflect.*;
+import java.sql.*;
+import com.mchange.v1.util.ClosableResource;
+
+public interface StatementCache extends ClosableResource
+{
+    public Object checkoutStatement( Connection physicalConnection,
+				     Method stmtProducingMethod, 
+				     Object[] args )
+	throws SQLException;
+
+    public void checkinStatement( Object pstmt )
+	throws SQLException;
+
+    public void checkinAll( Connection pcon )
+	throws SQLException;
+
+    public void closeAll( Connection pcon )
+	throws SQLException;
+
+    public void close() 
+	throws SQLException;
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/StatementCacheBenchmark.java b/src/classes/com/mchange/v2/c3p0/stmt/StatementCacheBenchmark.java
new file mode 100644
index 0000000..77b72d9
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/StatementCacheBenchmark.java
@@ -0,0 +1,175 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v1.db.sql.*;
+
+public final class StatementCacheBenchmark
+{
+    final static String EMPTY_TABLE_CREATE = "CREATE TABLE emptyyukyuk (a varchar(8), b varchar(8))";
+    final static String EMPTY_TABLE_SELECT = "SELECT * FROM emptyyukyuk";
+    final static String EMPTY_TABLE_DROP   = "DROP TABLE emptyyukyuk";
+
+    final static String EMPTY_TABLE_CONDITIONAL_SELECT = "SELECT * FROM emptyyukyuk where a = ?";
+
+    final static int NUM_ITERATIONS = 2000;
+
+    public static void main(String[] argv)
+    {
+	DataSource ds_unpooled = null;
+	DataSource ds_pooled   = null;
+	try
+	    {
+		
+		String jdbc_url = null;
+		String username = null;
+		String password = null;
+		if (argv.length == 3)
+		    {
+			jdbc_url = argv[0];
+			username = argv[1];
+			password = argv[2];
+		    }
+		else if (argv.length == 1)
+		    {
+			jdbc_url = argv[0];
+			username = null;
+			password = null;
+		    }
+		else
+		    usage();
+
+		if (! jdbc_url.startsWith("jdbc:") )
+		    usage();
+
+		ds_unpooled = DriverManagerDataSourceFactory.create(jdbc_url, username, password);
+		ds_pooled
+    		    = PoolBackedDataSourceFactory.create(jdbc_url, 
+    							 username, 
+    							 password,
+    							 5,
+    							 20,
+    							 5,
+    							 0,
+    							 100 );
+
+		create(ds_pooled);
+
+		perform( ds_pooled, "pooled" );
+		perform( ds_unpooled, "unpooled" );
+	    }
+	catch( Exception e )
+	    { e.printStackTrace(); }
+	finally
+	    {
+		try { drop(ds_pooled); }
+		catch (Exception e)
+		    { e.printStackTrace(); }
+	    }
+    }
+
+    private static void perform( DataSource ds, String name )
+	throws SQLException 
+    {
+	Connection c = null;
+	PreparedStatement ps = null;
+	try
+	    {
+		c = ds.getConnection();
+		long start = System.currentTimeMillis();
+		for (int i = 0; i < NUM_ITERATIONS; ++i)
+		    {
+			PreparedStatement test = 
+			    c.prepareStatement( EMPTY_TABLE_CONDITIONAL_SELECT );
+			test.close();
+		    }
+		long end = System.currentTimeMillis();
+		System.err.println(name + " --> " +
+				   (end - start) / (float) NUM_ITERATIONS + 
+				   " [" + NUM_ITERATIONS + " iterations]"); 
+	    }
+	finally
+	    {
+		StatementUtils.attemptClose( ps );
+		ConnectionUtils.attemptClose( c );
+	    }
+    }
+
+    private static void usage()
+    {
+	System.err.println("java " +
+			   "-Djdbc.drivers=<comma_sep_list_of_drivers> " +
+			   StatementCacheBenchmark.class.getName() +
+			   " <jdbc_url> [<username> <password>]" );
+	System.exit(-1);
+    }
+
+    static void create(DataSource ds)
+	throws SQLException
+    {
+	System.err.println("Creating test schema.");
+	Connection        con = null;
+	PreparedStatement ps1 = null;
+	try 
+	    { 
+		con = ds.getConnection();
+		ps1 = con.prepareStatement(EMPTY_TABLE_CREATE);
+		ps1.executeUpdate();
+		System.err.println("Test schema created.");
+	    }
+	finally
+	    {
+		StatementUtils.attemptClose( ps1 );
+		ConnectionUtils.attemptClose( con ); 
+	    }
+    }
+
+    static void drop(DataSource ds)
+	throws SQLException
+    {
+	Connection con        = null;
+	PreparedStatement ps1 = null;
+	try 
+	    { 
+		con = ds.getConnection();
+		ps1 = con.prepareStatement(EMPTY_TABLE_DROP);
+		ps1.executeUpdate();
+	    }
+	finally
+	    {
+		StatementUtils.attemptClose( ps1 );
+		ConnectionUtils.attemptClose( con ); 
+	    }
+	System.err.println("Test schema dropped.");
+    }
+}
+
+
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/StatementCacheKey.java b/src/classes/com/mchange/v2/c3p0/stmt/StatementCacheKey.java
new file mode 100644
index 0000000..ab2bea2
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/StatementCacheKey.java
@@ -0,0 +1,180 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.sql.Connection;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import com.mchange.v1.util.ArrayUtils;
+import com.mchange.v2.lang.ObjectUtils;
+
+abstract class StatementCacheKey
+{
+    static final int SIMPLE           = 0;
+    static final int MEMORY_COALESCED = 1;
+    static final int VALUE_IDENTITY   = 2;
+
+    //NOTE: subclasses rely upon their _find logic being protected by StatementCacheKey.class' lock!
+    public synchronized static StatementCacheKey find( Connection pcon, Method stmtProducingMethod, Object[] args )
+    {
+	switch ( VALUE_IDENTITY )
+	    {
+	    case SIMPLE:
+		return SimpleStatementCacheKey._find( pcon, stmtProducingMethod, args );
+	    case MEMORY_COALESCED:
+		return MemoryCoalescedStatementCacheKey._find( pcon, stmtProducingMethod, args );
+	    case VALUE_IDENTITY:
+		return ValueIdentityStatementCacheKey._find( pcon, stmtProducingMethod, args );
+	    default:
+		throw new InternalError("StatementCacheKey.find() is misconfigured.");
+	    }
+    }
+
+    //MT: instances are treated as immutable once they 
+    //    have been initialized and handed to
+    //    a client. (Factories may reinitialize
+    //    instances that never get released to
+    //    clients -- those factories must prevent
+    //    concurrent access to these recycled, 
+    //    nascent keys.)
+    Connection     physicalConnection;
+    String         stmtText;
+    boolean        is_callable;
+    int            result_set_type;
+    int            result_set_concurrency;
+
+    int[]          columnIndexes;          //jdbc3, null means default
+    String[]       columnNames;            //jdbc3, null means default
+
+    Integer        autogeneratedKeys;   //jdbc3, null means driver default, which the spec does not sepcify 
+    Integer        resultSetHoldability; //jdbc3, null means driver default, which the spec does not sepcify
+
+    StatementCacheKey()
+    {}
+
+    StatementCacheKey( Connection physicalConnection,
+		       String stmtText,
+		       boolean is_callable,
+		       int result_set_type,
+		       int result_set_concurrency,
+		       int[] columnIndexes,
+		       String[] columnNames,
+		       Integer autogeneratedKeys,
+		       Integer resultSetHoldability )
+    {
+	init( physicalConnection,
+	      stmtText,
+	      is_callable,
+	      result_set_type,
+	      result_set_concurrency,
+	      columnIndexes,
+	      columnNames,
+	      autogeneratedKeys,
+	      resultSetHoldability
+	      );
+    }
+
+    void init( Connection physicalConnection,
+	       String stmtText,
+	       boolean is_callable,
+	       int result_set_type,
+	       int result_set_concurrency,
+	       int[] columnIndexes,          //jdbc3
+	       String[] columnNames,         //jdbc3
+	       Integer autogeneratedKeys,    //jdbc3
+	       Integer resultSetHoldability) //jdbc3
+    {
+	this.physicalConnection     = physicalConnection;
+	this.stmtText               = stmtText;
+	this.is_callable            = is_callable;
+	this.result_set_type        = result_set_type;
+	this.result_set_concurrency = result_set_concurrency;
+	this.columnIndexes          = columnIndexes;
+	this.columnNames            = columnNames;
+	this.autogeneratedKeys      = autogeneratedKeys;
+	this.resultSetHoldability   = resultSetHoldability;
+    }
+    
+    static boolean equals(StatementCacheKey _this, Object o)
+    {
+	//TODO: assert( _this != null )
+
+	if ( _this == o )
+	    return true;
+	if (o instanceof StatementCacheKey)
+	    {
+		StatementCacheKey sck = (StatementCacheKey) o;
+
+// 		System.err.println( sck.physicalConnection + "   " + 
+// 				    _this.physicalConnection + "   equals? " + 
+// 				    sck.physicalConnection.equals( _this.physicalConnection ) );
+
+		return 
+		    sck.physicalConnection.equals(_this.physicalConnection) &&
+		    sck.stmtText.equals(_this.stmtText) &&
+		    sck.is_callable == _this.is_callable &&
+		    sck.result_set_type == _this.result_set_type &&
+		    sck.result_set_concurrency == _this.result_set_concurrency && 
+		    Arrays.equals( sck.columnIndexes, _this.columnIndexes ) &&
+		    Arrays.equals( sck.columnNames, _this.columnNames ) &&
+		    ObjectUtils.eqOrBothNull( sck.autogeneratedKeys, _this.autogeneratedKeys ) &&
+		    ObjectUtils.eqOrBothNull( sck.resultSetHoldability, _this.resultSetHoldability );
+	    }
+	else
+	    return false;
+    }
+    
+    static int hashCode(StatementCacheKey _this)
+    { 
+	return 
+	    _this.physicalConnection.hashCode() ^
+	    _this.stmtText.hashCode() ^
+	    (_this.is_callable ? 1 : 0) ^
+	    _this.result_set_type ^
+	    _this.result_set_concurrency ^
+	    ArrayUtils.hashOrZeroArray( _this.columnIndexes ) ^
+	    ArrayUtils.hashOrZeroArray( _this.columnNames ) ^
+	    ObjectUtils.hashOrZero( _this.autogeneratedKeys ) ^   //this is okay -- genuine constants are non-zer0
+	    ObjectUtils.hashOrZero( _this.resultSetHoldability ); //this is okay -- genuine constants are non-zer0
+    }
+
+    public String toString()
+    { 
+	StringBuffer out = new StringBuffer(128);
+	out.append("[" + this.getClass().getName() + ": ");
+	out.append("physicalConnection->" + physicalConnection);
+	out.append(", stmtText->" + stmtText);
+	out.append(", is_callable->" + is_callable);
+	out.append(", result_set_type->" + result_set_type);
+	out.append(", result_set_concurrency->" + result_set_concurrency);
+	out.append(", columnIndexes->" + ArrayUtils.toString(columnIndexes));
+	out.append(", columnNames->" + ArrayUtils.toString(columnNames));
+	out.append(", autogeneratedKeys->" + autogeneratedKeys);
+	out.append(", resultSetHoldability->" + resultSetHoldability);
+	out.append(']');
+	return out.toString();
+    }
+}
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/stmt/ValueIdentityStatementCacheKey.java b/src/classes/com/mchange/v2/c3p0/stmt/ValueIdentityStatementCacheKey.java
new file mode 100644
index 0000000..097e8d9
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/stmt/ValueIdentityStatementCacheKey.java
@@ -0,0 +1,202 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.stmt;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.lang.reflect.Method;
+import com.mchange.v2.coalesce.*;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.lang.reflect.Method;
+import com.mchange.v2.coalesce.*;
+
+final class ValueIdentityStatementCacheKey extends StatementCacheKey
+{
+    //MT: not thread-safe, but protected within the find() method
+    //    by StatementCacheKey.class lock
+    final static Coalescer keyCoalescer;
+
+    //MT: modified only within StatementCacheKey.class-locked find() method
+    static ValueIdentityStatementCacheKey spare = new ValueIdentityStatementCacheKey();
+
+    static 
+    {
+	CoalesceChecker cc = new CoalesceChecker()
+	    {
+		public boolean checkCoalesce( Object a, Object b )
+		{ return StatementCacheKey.equals( (StatementCacheKey) a, b ); }
+
+		public int coalesceHash( Object a )
+		{ return ((ValueIdentityStatementCacheKey) a).cached_hash; }
+	    }; 
+	
+	//make weak, unsync'ed coalescer
+	keyCoalescer = CoalescerFactory.createCoalescer( cc, true, false );
+    }
+
+    static StatementCacheKey _find( Connection pcon, Method stmtProducingMethod, Object[] args )
+    {
+	///BEGIN FIND LOGIC///
+	String stmtText = (String) args[0];
+	boolean is_callable = stmtProducingMethod.getName().equals("prepareCall");
+	int result_set_type;
+	int result_set_concurrency;
+
+	int[] columnIndexes;
+	String[] columnNames;
+	Integer autogeneratedKeys;
+	Integer resultSetHoldability;
+
+	if (args.length == 1)
+	    {
+		result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+		result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+		columnIndexes          = null;
+		columnNames            = null;
+		autogeneratedKeys      = null;
+		resultSetHoldability   = null;
+	    }
+	else if (args.length == 2)
+	    {
+		Class[] argTypes = stmtProducingMethod.getParameterTypes();
+		if (argTypes[1].isArray())
+		    {
+			Class baseType = argTypes[1].getComponentType();
+			if (baseType == int.class) //second arg is columnIndexes
+			    {
+				result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+				result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+				columnIndexes          = (int[]) args[1];
+				columnNames            = null;
+				autogeneratedKeys      = null;
+				resultSetHoldability   = null;
+			    }
+			else if (baseType == String.class)
+			    {
+				result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+				result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+				columnIndexes          = null;
+				columnNames            = (String[]) args[1];
+				autogeneratedKeys      = null;
+				resultSetHoldability   = null;
+			    }
+			else
+			    throw new IllegalArgumentException("c3p0 probably needs to be updated for some new " +
+							       "JDBC spec! As of JDBC3, we expect two arg statement " +
+							       "producing methods where the second arg is either " +
+							       "an int, int array, or String array.");
+		    }
+		else //it should be a boxed int, autogeneratedKeys
+		    {
+			result_set_type        = ResultSet.TYPE_FORWARD_ONLY;
+			result_set_concurrency = ResultSet.CONCUR_READ_ONLY;
+			columnIndexes          = null;
+			columnNames            = null;
+			autogeneratedKeys      = (Integer) args[1];
+			resultSetHoldability   = null;
+		    }
+	    }
+	else if (args.length == 3)
+	    {
+		result_set_type        = ((Integer) args[1]).intValue();
+		result_set_concurrency = ((Integer) args[2]).intValue();
+		columnIndexes          = null;
+		columnNames            = null;
+		autogeneratedKeys      = null;
+		resultSetHoldability   = null;
+	    }
+	else if (args.length == 4)
+	    {
+		result_set_type        = ((Integer) args[1]).intValue();
+		result_set_concurrency = ((Integer) args[2]).intValue();
+		columnIndexes          = null;
+		columnNames            = null;
+		autogeneratedKeys      = null;
+		resultSetHoldability   = (Integer) args[3];
+	    }
+	else
+	    throw new IllegalArgumentException("Unexpected number of args to " + 
+					       stmtProducingMethod.getName() );
+	///END FIND LOGIC///
+
+
+	// we keep around a "spare" and initialize it over and over again
+	// rather than allocating, because usually we'll find the statement we're
+	// looking for is already in the coalescer, and we can avoid the
+	// allocation altogether.
+  	spare.init( pcon, 
+		    stmtText, 
+		    is_callable, 
+		    result_set_type, 
+		    result_set_concurrency,
+		    columnIndexes,
+		    columnNames,
+		    autogeneratedKeys,
+		    resultSetHoldability );
+
+  	StatementCacheKey out = (StatementCacheKey) keyCoalescer.coalesce( spare );
+
+//   	System.err.println( "StatementCacheKey -> " + out );
+//   	System.err.println( "Key is coalesced already? " + (out != spare) );
+//   	System.err.println( "Keys in coalescer: " + keyCoalescer.countCoalesced() );
+
+	if (out == spare)
+	    spare = new ValueIdentityStatementCacheKey();
+	return out;
+    }
+
+    void init( Connection physicalConnection,
+	       String stmtText,
+	       boolean is_callable,
+	       int result_set_type,
+	       int result_set_concurrency,
+	       int[] columnIndexes,
+	       String[] columnNames,
+	       Integer autogeneratedKeys,
+	       Integer resultSetHoldability )
+    {
+	super.init( physicalConnection,
+		    stmtText,
+		    is_callable,
+		    result_set_type,
+		    result_set_concurrency,
+		    columnIndexes,
+		    columnNames,
+		    autogeneratedKeys,
+		    resultSetHoldability );
+	this.cached_hash = StatementCacheKey.hashCode( this );
+    }
+
+    // extra instance varieable
+    int cached_hash;
+
+    // Note that we DON'T override equals() or hashCode() here -- each instance let the coalescer guarantee a
+    // single instance exists that would equals() it (that is, itself), and we rely on Object's default equals() and
+    // hashCode methods do their thangs.
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/subst/C3P0Substitutions.java b/src/classes/com/mchange/v2/c3p0/subst/C3P0Substitutions.java
new file mode 100644
index 0000000..195bf6b
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/subst/C3P0Substitutions.java
@@ -0,0 +1,35 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.subst;
+
+public final class C3P0Substitutions
+{
+    public final static String VERSION    = "@c3p0.version@";
+    public final static String DEBUG      = "@c3p0.debug@";
+    public final static String TRACE      = "@c3p0.trace@";
+    public final static String TIMESTAMP  = "@c3p0.timestamp@";
+
+    private C3P0Substitutions()
+    {}
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/test/AlwaysFailConnectionTester.java b/src/classes/com/mchange/v2/c3p0/test/AlwaysFailConnectionTester.java
new file mode 100644
index 0000000..bf6c4c7
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/AlwaysFailConnectionTester.java
@@ -0,0 +1,64 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.sql.Connection;
+import com.mchange.v2.c3p0.QueryConnectionTester;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+
+public final class AlwaysFailConnectionTester implements QueryConnectionTester
+{
+    final static MLogger logger = MLog.getLogger( AlwaysFailConnectionTester.class );
+
+    {
+	logger.log(MLevel.WARNING,  "Instantiated: " + this, new Exception("Instantiation Stack Trace.") );
+    }
+
+    public int activeCheckConnection(Connection c)
+    {
+	logger.warning(this + ": activeCheckConnection(Connection c)");
+	return CONNECTION_IS_INVALID; 
+    }
+
+    public int statusOnException(Connection c, Throwable t)
+    { 
+	logger.warning(this + ": statusOnException(Connection c, Throwable t)");
+	return CONNECTION_IS_INVALID; 
+    }
+
+    public int activeCheckConnection(Connection c, String preferredTestQuery)
+    { 
+	logger.warning(this + ": activeCheckConnection(Connection c, String preferredTestQuery)");
+	return CONNECTION_IS_INVALID; 
+    }
+
+    public boolean equals( Object o )
+    { return (o instanceof AlwaysFailConnectionTester); }
+
+    public int hashCode()
+    { return 1; }
+}
+
diff --git a/src/classes/com/mchange/v2/c3p0/test/C3P0BenchmarkApp.java b/src/classes/com/mchange/v2/c3p0/test/C3P0BenchmarkApp.java
new file mode 100644
index 0000000..c7c4121
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/C3P0BenchmarkApp.java
@@ -0,0 +1,732 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v1.db.sql.*;
+import com.mchange.v2.c3p0.DriverManagerDataSource;
+
+public final class C3P0BenchmarkApp
+{
+    final static String EMPTY_TABLE_CREATE = "CREATE TABLE emptyyukyuk (a varchar(8), b varchar(8))";
+    final static String EMPTY_TABLE_SELECT = "SELECT * FROM emptyyukyuk";
+    final static String EMPTY_TABLE_DROP   = "DROP TABLE emptyyukyuk";
+    
+    final static String EMPTY_TABLE_CONDITIONAL_SELECT = "SELECT * FROM emptyyukyuk where a = ?";
+
+    final static String N_ENTRY_TABLE_CREATE = "CREATE TABLE n_entryyukyuk (a INTEGER)";
+    final static String N_ENTRY_TABLE_INSERT = "INSERT INTO n_entryyukyuk VALUES ( ? )";
+    final static String N_ENTRY_TABLE_SELECT = "SELECT * FROM n_entryyukyuk";
+    final static String N_ENTRY_TABLE_DROP   = "DROP TABLE n_entryyukyuk";
+
+    //final static int NUM_ITERATIONS = 20;
+    final static int NUM_ITERATIONS = 2000;
+    //final static int NUM_ITERATIONS = 10000;
+    //final static int NUM_ITERATIONS = 20000;
+    //final static int NUM_ITERATIONS = 100000;
+
+    public static void main(String[] argv)
+    {
+        if (argv.length > 0)
+        {
+            System.err.println( C3P0BenchmarkApp.class.getName() + 
+                                " now requires no args. Please set everything in standard c3p0 config files.");
+            return;                    
+        }
+        
+//      com.mchange.v2.log.MLog.getLogger( C3P0BenchmarkApp.class ).info("this is some info.");
+// 	com.mchange.v2.log.MLog.getLogger( C3P0BenchmarkApp.class ).log(com.mchange.v2.log.MLevel.WARNING, "this is a warning.", new Exception("test"));
+// 	com.mchange.v2.log.MLog.getLogger( C3P0BenchmarkApp.class ).log(com.mchange.v2.log.MLevel.FINE, "this is fine.");
+
+// 	System.getProperties().put("sprong", java.awt.Color.blue);
+// 	System.getProperties().put(java.awt.Color.blue, "sprong");
+
+
+	DataSource ds_unpooled = null;
+	DataSource ds_pooled   = null;
+	try
+	    {
+/*		
+		String jdbc_url = null;
+		String username = null;
+		String password = null;
+		if (argv.length == 3)
+		    {
+			jdbc_url = argv[0];
+			username = argv[1];
+			password = argv[2];
+		    }
+		else if (argv.length == 1)
+		    {
+			jdbc_url = argv[0];
+			username = null;
+			password = null;
+		    }
+		else
+		    usage();
+
+		if (! jdbc_url.startsWith("jdbc:") )
+		    usage();
+*/
+        
+//  		ds_unpooled = DriverManagerDataSourceFactory.create(jdbc_url, username, password);
+
+//  		ds_pooled
+//  //  		    = PoolBackedDataSourceFactory.create(jdbc_url, username, password);
+//      		    = PoolBackedDataSourceFactory.create(jdbc_url, 
+//      							 username, 
+//      							 password,
+//      							 5,
+//      							 20,
+//      							 5,
+//      							 0,
+//      							 100 );
+
+		//ds_unpooled = DataSources.unpooledDataSource(jdbc_url, username, password);
+		//ds_pooled = DataSources.pooledDataSource( ds_unpooled );
+      ds_unpooled = new DriverManagerDataSource();
+
+   		//DataSource ds_unpooled_screwy = C3P0TestUtils.unreliableCommitDataSource( ds_unpooled );
+   		//ds_pooled = DataSources.pooledDataSource( ds_unpooled_screwy );
+
+// 		PoolConfig pc = new PoolConfig();
+// 		pc.setMaxStatements(200);
+// 		pc.setCheckoutTimeout(500);
+//  		ds_pooled = DataSources.pooledDataSource( ds_unpooled, pc );
+//  		ds_pooled = DataSources.pooledDataSource( ds_unpooled, "foo", "goo" );
+
+      //ds_pooled = DataSources.pooledDataSource(ds_unpooled);
+        
+		//ComboPooledDataSource cpds = new ComboPooledDataSource("dumbTestConfig");
+ 		ComboPooledDataSource cpds = new ComboPooledDataSource();
+ 		//cpds.setJdbcUrl( jdbc_url );
+ 		//cpds.setUser( username );
+ 		//cpds.setPassword( password );
+ 		ds_pooled = cpds;
+        
+//        ComboPooledDataSource cpds2 = new ComboPooledDataSource();
+//        System.err.println("Made second ComboPooledDataSource.");
+//        cpds.getNumIdleConnectionsDefaultUser();
+//        cpds2.getNumIdleConnectionsDefaultUser();
+        
+//        Properties badProps = new Properties();
+//        badProps.put("badprop", null);
+//        DataSource appendix = DataSources.pooledDataSource(ds_unpooled, badProps);
+
+ 		create(ds_pooled);
+
+		System.out.println("Please wait. Tests can be very slow.");
+		List l = new ArrayList();
+ 		l.add( new ConnectionAcquisitionTest() );
+  		l.add( new StatementCreateTest() );
+   		l.add( new StatementEmptyTableSelectTest() );
+   		//l.add( new DataBaseMetaDataListNonexistentTablesTest() );
+   		l.add( new PreparedStatementEmptyTableSelectTest() );
+ 		l.add( new PreparedStatementAcquireTest() );
+   		l.add( new ResultSetReadTest() );
+   		l.add( new FiveThreadPSQueryTestTest() );
+		for (int i = 0, len = l.size(); i < len; ++i)
+		    ((Test) l.get(i)).perform( ds_unpooled, ds_pooled, NUM_ITERATIONS );
+	    }
+	catch( Throwable t )
+	    {
+		System.err.print("Aborting tests on Throwable -- ");
+		t.printStackTrace(); 
+		if (t instanceof Error)
+		    throw (Error) t;
+	    }
+	finally
+	    {
+		//System.err.println( "pooled data sources: " + C3P0Registry.getPooledDataSources() );
+
+ 		try { drop(ds_pooled); }
+		catch (Exception e)
+		    { e.printStackTrace(); }
+
+ 		try { DataSources.destroy(ds_pooled); }
+		catch (Exception e)
+		    { e.printStackTrace(); }
+
+ 		try { DataSources.destroy(ds_unpooled); }
+		catch (Exception e)
+		    { e.printStackTrace(); }
+	    }
+    }
+
+    /*
+    private static void usage()
+    {
+	System.err.println("java " +
+			   "-Djdbc.drivers=<comma_sep_list_of_drivers> " +
+			   C3P0BenchmarkApp.class.getName() +
+			   " <jdbc_url> [<username> <password>]" );
+	System.exit(-1);
+    }
+*/
+    static void create(DataSource ds)
+	throws SQLException
+    {
+	System.err.println("Creating test schema.");
+	Connection        con = null;
+	PreparedStatement ps1 = null;
+	PreparedStatement ps2 = null;
+	PreparedStatement ps3 = null;
+	try 
+	    { 
+		con = ds.getConnection();
+		ps1 = con.prepareStatement(EMPTY_TABLE_CREATE);
+		ps2 = con.prepareStatement(N_ENTRY_TABLE_CREATE);
+		ps3 = con.prepareStatement(N_ENTRY_TABLE_INSERT);
+
+		ps1.executeUpdate();
+		ps2.executeUpdate();
+
+  		for (int i = 0; i < NUM_ITERATIONS; ++i)
+   		    {
+   			ps3.setInt(1, i );
+   			ps3.executeUpdate();
+   			System.err.print('.');
+   		    }
+		System.err.println();
+		System.err.println("Test schema created.");
+	    }
+	finally
+	    {
+		StatementUtils.attemptClose( ps1 );
+		StatementUtils.attemptClose( ps2 );
+		StatementUtils.attemptClose( ps3 );
+		ConnectionUtils.attemptClose( con ); 
+	    }
+    }
+
+    static void drop(DataSource ds)
+	throws SQLException
+    {
+	Connection con        = null;
+	PreparedStatement ps1 = null;
+	PreparedStatement ps2 = null;
+	try 
+	    { 
+		con = ds.getConnection();
+		ps1 = con.prepareStatement(EMPTY_TABLE_DROP);
+		ps2 = con.prepareStatement(N_ENTRY_TABLE_DROP);
+
+		ps1.executeUpdate();
+		ps2.executeUpdate();
+
+		// should be superfluous 'cuz should be autocommit
+		//con.commit();
+
+		System.err.println("Test schema dropped.");
+	    }
+	finally
+	    {
+		StatementUtils.attemptClose( ps1 );
+		StatementUtils.attemptClose( ps2 );
+		ConnectionUtils.attemptClose( con ); 
+	    }
+    }
+
+    static abstract class Test
+    {
+	String name;
+	
+	Test(String name)
+	{ this.name = name; }
+
+	public void perform(DataSource unpooled, DataSource pooled, int iterations) throws Exception
+	{
+	    double msecs_unpooled = test(unpooled, iterations) / ((double) iterations);
+	    double msecs_pooled = test(pooled, iterations) / ((double) iterations);
+	    System.out.println(name + " [ " + iterations + " iterations ]:");
+	    System.out.println('\t' + "unpooled: " + msecs_unpooled + " msecs");
+	    System.out.println('\t' + "  pooled: " + msecs_pooled + " msecs");
+	    System.out.println('\t' + "speed-up factor: " + msecs_unpooled / msecs_pooled + " times");
+	    System.out.println('\t' + "speed-up absolute: " + (msecs_unpooled - msecs_pooled)  + 
+			       " msecs");
+	    System.out.println();
+
+// 	    PooledDataSource pds = (PooledDataSource) pooled;
+// 	    System.out.println( pds.getNumConnections() );
+// 	    System.out.println( pds.getNumIdleConnections() );
+// 	    System.out.println( pds.getNumBusyConnections() );
+// 	    System.out.println( pds.getNumConnectionsAllAuths() );
+	}
+
+	protected abstract long test(DataSource ds, int n) throws Exception;
+    }
+
+    static class ConnectionAcquisitionTest extends Test
+    {
+	ConnectionAcquisitionTest()
+	{ super("Connection Acquisition and Cleanup"); }
+
+	protected long test(DataSource ds, int n) throws Exception
+	{
+	    long start;
+	    long end;
+	    
+	    start = System.currentTimeMillis();
+	    for (int i = 0; i < n; ++i)
+		{
+		    Connection con = null;
+		    try
+			{ con = ds.getConnection(); }
+		    finally
+			{ ConnectionUtils.attemptClose( con ); }
+		    //System.err.print(i + "\t");
+		}
+	    end = System.currentTimeMillis();
+	    return end - start;
+	}
+    }
+
+    static class StatementCreateTest extends Test
+    {
+	StatementCreateTest()
+	{ super("Statement Creation and Cleanup"); }
+
+	protected long test(DataSource ds, int n) throws SQLException
+	{
+	    Connection con = null;
+	    try 
+		{ 
+		    con = ds.getConnection();
+		    return test( con , n );
+		}
+	    finally
+		{ ConnectionUtils.attemptClose( con ); }
+	    //{}
+	}
+
+	long test(Connection con, int n) throws SQLException
+	{ 
+	    long start;
+	    long end;
+	    
+	    Statement stmt = null;
+	    start = System.currentTimeMillis();
+	    for (int i = 0; i < n; ++i)
+		{
+		    try
+			{ stmt = con.createStatement();	}
+		    finally
+			{ StatementUtils.attemptClose( stmt ); }
+		}
+	    end = System.currentTimeMillis();
+	    return end - start;
+	}
+    }
+
+
+    static class StatementEmptyTableSelectTest extends Test
+    {
+	StatementEmptyTableSelectTest()
+	{ super("Empty Table Statement Select (on a single Statement)"); }
+
+	protected long test(DataSource ds, int n) throws SQLException
+	{
+	    Connection con  = null;
+	    Statement  stmt = null;
+	    try 
+		{ 
+		    con = ds.getConnection();
+		    stmt = con.createStatement();
+		    //System.err.println( stmt.getClass().getName() );
+		    return test( stmt , n );
+		}
+	    finally
+		{ 
+		    StatementUtils.attemptClose( stmt ); 
+		    ConnectionUtils.attemptClose( con ); 
+		}
+	}
+
+	long test(Statement stmt, int n) throws SQLException
+	{ 
+	    long start;
+	    long end;
+	    
+	    start = System.currentTimeMillis();
+	    for (int i = 0; i < n; ++i)
+		stmt.executeQuery(EMPTY_TABLE_SELECT).close();
+	    end = System.currentTimeMillis();
+	    return end - start;
+	}
+    }
+
+    static class DataBaseMetaDataListNonexistentTablesTest extends Test
+    {
+	DataBaseMetaDataListNonexistentTablesTest()
+	{ super("DataBaseMetaDataListNonexistentTablesTest"); }
+
+	protected long test(DataSource ds, int n) throws SQLException
+	{
+	    Connection con  = null;
+	    Statement  stmt = null;
+	    try 
+		{ 
+		    con = ds.getConnection();
+		    return test( con , n );
+		}
+	    finally
+		{ 
+		    StatementUtils.attemptClose( stmt ); 
+		    ConnectionUtils.attemptClose( con ); 
+		}
+	}
+
+	long test(Connection con, int n) throws SQLException
+	{ 
+	    ResultSet rs = null;
+
+	    try
+		{
+		    long start;
+		    long end;
+		    
+		    start = System.currentTimeMillis();
+		    for (int i = 0; i < n; ++i)
+			rs = con.getMetaData().getTables( null, 
+							null, 
+							"PROBABLYNOT", 
+							new String[] {"TABLE"} );
+		    end = System.currentTimeMillis();
+		    return end - start;
+		}
+	finally
+	    { ResultSetUtils.attemptClose( rs ); }
+	}
+    }
+
+    static class PreparedStatementAcquireTest extends Test
+    {
+	PreparedStatementAcquireTest()
+	{ super("Acquire and Cleanup a PreparedStatement (same statement, many times)"); }
+
+	protected long test(DataSource ds, int n) throws SQLException
+	{
+	    long start;
+	    long end;
+	    
+	    Connection        con   = null;
+	    PreparedStatement pstmt = null;
+	    try 
+		{ 
+		    con = ds.getConnection();
+		    start = System.currentTimeMillis();
+		    for (int i = 0; i < n; ++i)
+			{
+			    try
+   				{ pstmt = con.prepareStatement(EMPTY_TABLE_CONDITIONAL_SELECT); }
+
+/*
+    Leftover random abuses from ad hoc testing...
+
+ 				{
+ 				    pstmt = con.prepareStatement(EMPTY_TABLE_CONDITIONAL_SELECT, 
+ 								 ResultSet.TYPE_SCROLL_SENSITIVE, 
+ 								 ResultSet.CONCUR_UPDATABLE, 
+ 								 ResultSet.HOLD_CURSORS_OVER_COMMIT);
+ 				}
+
+
+  				{ pstmt = con.prepareStatement(N_ENTRY_TABLE_INSERT); }
+*/
+			    finally
+				{ StatementUtils.attemptClose( pstmt ); }
+			}
+		    end = System.currentTimeMillis();
+		    return end - start;
+		}
+	    finally
+		{ ConnectionUtils.attemptClose( con ); 	}
+	}
+    }
+
+    static class PreparedStatementEmptyTableSelectTest extends Test
+    {
+	PreparedStatementEmptyTableSelectTest()
+	{ super("Empty Table PreparedStatement Select (on a single PreparedStatement)"); }
+
+	protected long test(DataSource ds, int n) throws SQLException
+	{
+	    Connection        con   = null;
+	    PreparedStatement pstmt = null;
+	    try 
+		{ 
+		    con = ds.getConnection();
+		    pstmt = con.prepareStatement(EMPTY_TABLE_SELECT);
+
+// 		    Leftover from ad-hoc testing...
+//
+// 		    pstmt = con.prepareStatement(EMPTY_TABLE_SELECT, 
+// 						 ResultSet.TYPE_SCROLL_SENSITIVE, 
+// 						 ResultSet.CONCUR_UPDATABLE, 
+// 						 ResultSet.HOLD_CURSORS_OVER_COMMIT);
+		    return test( pstmt , n );
+		}
+	    finally
+		{ 
+		    StatementUtils.attemptClose( pstmt ); 
+		    ConnectionUtils.attemptClose( con ); 
+		}
+	}
+
+	long test(PreparedStatement pstmt, int n) throws SQLException
+	{ 
+	    long start;
+	    long end;
+	    
+	    start = System.currentTimeMillis();
+	    for (int i = 0; i < n; ++i)
+		pstmt.executeQuery().close();
+	    end = System.currentTimeMillis();
+	    return end - start;
+	}
+    }
+
+    static class ResultSetReadTest extends Test
+    {
+  	ResultSetReadTest()
+  	{ super("Reading one row / one entry from a result set"); }
+
+  	protected long test(DataSource ds, int n) throws SQLException
+  	{
+	    if (n > 10000)
+		throw new IllegalArgumentException("10K max.");
+
+  	    long start;
+  	    long end;
+	    
+  	    Connection        con   = null;
+  	    PreparedStatement pstmt = null;
+  	    ResultSet         rs    = null;
+	    
+	    try
+		{
+		    con = ds.getConnection();
+		    pstmt = con.prepareStatement(N_ENTRY_TABLE_SELECT);
+		    rs = pstmt.executeQuery();
+
+		    start = System.currentTimeMillis();
+		    for (int i = 0; i < n; ++i)
+			{
+			    if (! rs.next() )
+				System.err.println("huh?");
+			    rs.getInt(1);
+			}
+		    end = System.currentTimeMillis();
+		    return end - start;
+		}
+	    finally
+		{ 
+		    ResultSetUtils.attemptClose( rs ); 
+		    StatementUtils.attemptClose( pstmt ); 
+		    ConnectionUtils.attemptClose( con ); 
+		}
+	}
+    }
+
+    static class FiveThreadPSQueryTestTest extends Test
+    {
+	// only for stupid test to simulate (illegal) concurrent access to a Statement
+// 	volatile Statement stmt;
+
+  	FiveThreadPSQueryTestTest()
+  	{ 
+	    super( "Five threads getting a connection, executing a query, " + 
+		   System.getProperty( "line.separator" ) +
+		   "and retrieving results concurrently via a prepared statement (in a transaction)." ); 
+	}
+
+  	protected long test(final DataSource ds, final int n) throws Exception
+  	{
+	    class QueryThread extends Thread
+	    {
+		QueryThread(int num)
+		{ super("QueryThread-" + num);}
+
+		public void run()
+		{
+		    Connection        con   = null;
+		    PreparedStatement pstmt = null;
+		    ResultSet         rs    = null;
+		    
+		    for (int i = 0; i < (n / 5); ++i)
+			{
+			    try
+				{
+				    con = ds.getConnection();
+
+// 				    System.err.println("before txn isolation set: " + con.getTransactionIsolation());
+// 				    con.setTransactionIsolation( Connection.TRANSACTION_SERIALIZABLE );
+// 				    //con.setTransactionIsolation( Connection.TRANSACTION_READ_UNCOMMITTED );
+// 				    System.err.println("after txn isolation set: " + con.getTransactionIsolation());
+
+				    con.setAutoCommit( false );
+
+				    pstmt = con.prepareStatement( EMPTY_TABLE_CONDITIONAL_SELECT );
+
+// 				    if (Math.random() < 0.5)
+// 					stmt = pstmt;
+// 				    else if (stmt != null)
+// 					stmt.getResultSet();
+
+//  				    if (Math.random() < 0.1 && con instanceof C3P0ProxyConnection)
+//  					con.close();
+
+				    pstmt.setString(1, "boo");
+				    rs = pstmt.executeQuery();
+				    while( rs.next() )
+					System.err.println("Huh?? Empty table has values?");
+				    //System.out.println(this + "   " + i);
+
+// 				    if (ds instanceof PooledDataSource)
+// 					{
+// 					    PooledDataSource pds = (PooledDataSource) ds;
+// 					    System.err.println("numConnections: " + pds.getNumConnections() );
+// 					    System.err.println("numIdleConnections: " + pds.getNumIdleConnections() );
+// 					    System.err.println("numBusyConnections: " + pds.getNumBusyConnections() );
+// 					    System.err.println();
+// 					}
+
+				    con.commit();
+				}
+			    catch (Exception e)
+				{ 
+				    System.err.print("FiveThreadPSQueryTestTest exception -- ");
+				    e.printStackTrace(); 
+				    try { if (con != null) con.rollback(); }
+				    catch (SQLException e2)
+					{
+					    System.err.print("Rollback on exception failed! -- ");
+					    e2.printStackTrace();
+					}
+				}
+			    finally
+				{
+				    ResultSetUtils.attemptClose( rs ); 
+				    StatementUtils.attemptClose( pstmt );
+				    ConnectionUtils.attemptClose( con ); 
+				    con = null;
+
+// 				    StatementUtils.attemptClose( pstmt ); //dup close
+// 				    ConnectionUtils.attemptClose( con ); //dup close
+// 				    try { System.err.println( pstmt.getConnection() ); } catch (Exception e) {e.printStackTrace();}
+// 				    ResultSetUtils.attemptClose( rs ); 
+				}
+			}
+		    //System.out.println(this + " finished.");
+		}
+	    }
+
+	    long start = System.currentTimeMillis();
+
+	    Thread[] ts = new Thread[5];
+	    for (int i = 0; i < 5; ++i)
+		{
+		    ts[i] = new QueryThread(i);
+		    ts[i].start();
+		}
+	    for (int i = 0; i < 5; ++i)
+		ts[i].join();
+
+	    return System.currentTimeMillis() - start;
+	}
+
+    }
+
+
+//      static class TenByTwoResultSetReadTest extends Test
+//      {
+//  	TenByTwoResultSetReadTest()
+//  	{ super("Reading all entryies from a 10 row 2 col result set"); }
+
+//  	protected long test(DataSource ds, int n) throws SQLException
+//  	{
+//  	    long start;
+//  	    long end;
+	    
+//  	    long start_ctrl;
+//  	    long end_ctrl;
+
+//  	    Connection        con   = null;
+//  	    PreparedStatement pstmt = null;
+//  	    ResultSet         rs    = null;
+	    
+//  	    start = System.currentTimeMillis();
+//  	    for (int i = 0; i < n; ++i)
+//  		{
+//  		    try
+//  			{
+//  			    con = ds.getConnection();
+//  			    pstmt = con.prepareStatement(N_ENTRY_TABLE_SELECT);
+//  			    rs = pstmt.executeQuery();
+//  			    while( rs.next() )
+//  				{
+//  				    rs.getInt(1);
+//  				    rs.getInt(2);
+//  				}
+//  			}
+//  		    finally
+//  			{ 
+//  			    ResultSetUtils.attemptClose( rs ); 
+//  			    StatementUtils.attemptClose( pstmt ); 
+//  			    ConnectionUtils.attemptClose( con ); 
+//  			}
+//  		}
+//  	    end = System.currentTimeMillis();
+
+
+//  	    start_ctrl = System.currentTimeMillis();
+//  	    for (int i = 0; i < n; ++i)
+//  		{
+//  		    try
+//  			{
+//  			    con = ds.getConnection();
+//  			    pstmt = con.prepareStatement(N_ENTRY_TABLE_SELECT);
+//  			    rs = pstmt.executeQuery();
+//  			}
+//  		    finally
+//  			{ 
+//  			    ResultSetUtils.attemptClose( rs ); 
+//  			    StatementUtils.attemptClose( pstmt ); 
+//  			    ConnectionUtils.attemptClose( con ); 
+//  			}
+//  		}
+//  	    end_ctrl = System.currentTimeMillis();
+
+//  	    return (end - start) - (end_ctrl - start_ctrl);
+//  	}
+//      }
+}
+
+
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/test/ConnectionDispersionTest.java b/src/classes/com/mchange/v2/c3p0/test/ConnectionDispersionTest.java
new file mode 100644
index 0000000..cc711a9
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/ConnectionDispersionTest.java
@@ -0,0 +1,233 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v1.db.sql.*;
+
+public final class ConnectionDispersionTest
+{
+    private final static int DELAY_TIME = 120000;
+    //private final static int DELAY_TIME = 300000;
+
+    private final static int NUM_THREADS = 600;
+    //private final static int NUM_THREADS = 300;
+    //private final static int NUM_THREADS = 50;
+
+    private final static Integer ZERO = new Integer(0);
+
+    private static boolean should_go = false;
+
+    private static DataSource cpds;
+
+    private static int ready_count = 0;
+
+    private static synchronized void setDataSource(DataSource ds)
+    { cpds = ds; }
+
+    private static synchronized DataSource getDataSource()
+    { return cpds; }
+
+    private static synchronized int ready()
+    { return ++ready_count; }
+
+    private static synchronized boolean isReady()
+    { return ready_count == NUM_THREADS; }
+
+    private static synchronized void start()
+    {
+	should_go = true;
+	ConnectionDispersionTest.class.notifyAll();
+    }
+
+    private static synchronized void stop()
+    {
+	should_go = false;
+	ConnectionDispersionTest.class.notifyAll();
+    }
+
+    private static synchronized boolean shouldGo()
+    { return should_go; }
+
+    public static void main(String[] argv)
+    {
+	String jdbc_url = null;
+	String username = null;
+	String password = null;
+	if (argv.length == 3)
+	    {
+		jdbc_url = argv[0];
+		username = argv[1];
+		password = argv[2];
+	    }
+	else if (argv.length == 1)
+	    {
+		jdbc_url = argv[0];
+		username = null;
+		password = null;
+	    }
+	else
+	    usage();
+	
+	if (! jdbc_url.startsWith("jdbc:") )
+	    usage();
+	
+	try
+	    {
+		ComboPooledDataSource ds = new ComboPooledDataSource();
+		ds.setJdbcUrl( jdbc_url );
+		ds.setUser( username );
+		ds.setPassword( password );
+		setDataSource( ds );
+
+		List threads = new ArrayList( NUM_THREADS );
+
+		for (int i = 0; i < NUM_THREADS; ++i)
+		    {
+			Thread t = new CompeteThread();
+			t.start();
+			threads.add( t );
+			Thread.currentThread().yield();
+		    }
+
+		synchronized ( ConnectionDispersionTest.class )
+		    { while (! isReady()) ConnectionDispersionTest.class.wait(); }
+
+		System.err.println("Starting the race.");
+		start();
+
+		System.err.println("Sleeping " + ((float) DELAY_TIME/1000) + 
+				   " seconds to let the race run");
+		Thread.sleep(DELAY_TIME);
+		System.err.println("Stopping the race.");
+		stop();
+		for (int i = 0; i < NUM_THREADS; ++i)
+		    ((Thread) threads.get(i)).join();
+
+		Map outcomeMap = new TreeMap();
+		for (int i = 0; i < NUM_THREADS; ++i)
+		    {
+			Integer outcome = new Integer( ((CompeteThread) threads.get(i)).getCount() );
+			Integer old = (Integer) outcomeMap.get( outcome );
+			if (old == null)
+			    old = ZERO;
+			outcomeMap.put( outcome, new Integer(old.intValue() + 1) );
+		    }
+
+		int last = 0;
+		for (Iterator ii = outcomeMap.keySet().iterator(); ii.hasNext(); )
+		    {
+			Integer outcome = (Integer) ii.next();
+			Integer count = (Integer) outcomeMap.get( outcome );
+			int oc = outcome.intValue();
+			int c = count.intValue();
+			for (; last < oc; ++last)
+			    System.out.println(String.valueOf(10000 + last).substring(1) + ": ");
+			++last;
+			System.out.print(String.valueOf(10000 + oc).substring(1) + ": ");
+// 			if (oc < 10)
+// 			    System.out.print(' ');
+			for(int i = 0; i < c; ++i)
+			    System.out.print('*');
+			System.out.println();
+		    }
+		
+// 		List outcomes = new ArrayList(NUM_THREADS);
+// 		for (int i = 0; i < NUM_THREADS; ++i)
+// 		    outcomes.add( new Integer( ((CompeteThread) threads.get(i)).getCount() ) );
+// 		Collections.sort( outcomes );
+		
+// 		System.out.println("Connection counts:");
+// 		for (int i = 0; i < NUM_THREADS; ++i)
+// 		    System.out.println( outcomes.get(i) + "  (" + i + ")");
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    static class CompeteThread extends Thread
+    {
+	DataSource ds;
+	int count;
+
+	synchronized void increment()
+	{ ++count; }
+
+	synchronized int getCount()
+	{ return count; }
+
+	public void run()
+	{
+	    try
+		{
+		    this.ds = getDataSource();
+		    synchronized ( ConnectionDispersionTest.class )
+			{
+			    ready();
+			    ConnectionDispersionTest.class.wait();
+			}
+		    while ( shouldGo() )
+			{
+			    Connection c = null;
+			    ResultSet rs = null;
+			    try
+				{ 
+				    c = ds.getConnection();
+				    increment();
+				    rs = c.getMetaData().getTables( null, 
+								    null, 
+								    "PROBABLYNOT", 
+								    new String[] {"TABLE"} );
+				}
+			    catch (SQLException e)
+				{ e.printStackTrace(); }
+			    finally
+				{ 
+				    try {if (rs != null) rs.close(); }
+				    catch (Exception e)
+					{ e.printStackTrace(); }
+				    
+				    try {if (c != null) c.close(); }
+				    catch (Exception e)
+					{ e.printStackTrace(); }
+				}
+			}
+		}
+	    catch (Exception e)
+		{ e.printStackTrace(); }
+	}
+    }
+
+    private static void usage()
+    {
+	System.err.println("java " +
+			   "-Djdbc.drivers=<comma_sep_list_of_drivers> " +
+			   ConnectionDispersionTest.class.getName() +
+			   " <jdbc_url> [<username> <password>]" );
+	System.exit(-1);
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/FreezableDriverManagerDataSource.java b/src/classes/com/mchange/v2/c3p0/test/FreezableDriverManagerDataSource.java
new file mode 100644
index 0000000..f172ab3
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/FreezableDriverManagerDataSource.java
@@ -0,0 +1,283 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.PrintWriter;
+import java.util.Properties;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+import com.mchange.v2.sql.SqlUtils;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import com.mchange.v2.c3p0.cfg.C3P0Config;
+import com.mchange.v2.c3p0.impl.DriverManagerDataSourceBase;
+
+
+// this is a copy-paste hack job to do a quick simulation of how c3p0 responds when
+// getConnection() freezes but does not fail.
+public final class FreezableDriverManagerDataSource extends DriverManagerDataSourceBase implements DataSource
+{
+    final static MLogger logger = MLog.getLogger( FreezableDriverManagerDataSource.class );
+    
+    final static File FREEZE_FILE = new File("/tmp/c3p0_freeze_file");
+
+    //MT: protected by this' lock
+    Driver driver;
+    
+    //MT: protected by this' lock
+    boolean driver_class_loaded = false;
+
+    public FreezableDriverManagerDataSource()
+    { this( true ); }
+
+    public FreezableDriverManagerDataSource(boolean autoregister)
+    {
+        super( autoregister );
+
+        setUpPropertyListeners();
+
+        String user = C3P0Config.initializeStringPropertyVar("user", null);
+        String password = C3P0Config.initializeStringPropertyVar("password", null);
+
+        if (user != null)
+            this.setUser( user );
+
+        if (password != null)
+            this.setPassword( password );
+    }
+    
+    private void waitNoFreezeFile() throws SQLException
+    {
+        try
+        {
+            while (true)
+            {
+                if (! FREEZE_FILE.exists())
+                    break;
+                Thread.sleep(1000);
+            }
+        }
+        catch (InterruptedException e)
+        {
+            logger.log(MLevel.WARNING, "Frozen cxn acquire interrupted.", e);
+            throw new SQLException( e.toString() );
+        }
+    }
+
+    private void setUpPropertyListeners()
+    {
+        PropertyChangeListener driverClassListener = new PropertyChangeListener()
+        {
+            public void propertyChange( PropertyChangeEvent evt )
+            {
+                Object val = evt.getNewValue();
+                if ( "driverClass".equals( evt.getPropertyName() ) )
+                    setDriverClassLoaded( false );
+            }
+        };
+        this.addPropertyChangeListener( driverClassListener );
+    }
+    
+    private synchronized boolean isDriverClassLoaded()
+    { return driver_class_loaded; }
+    
+    private synchronized void setDriverClassLoaded(boolean dcl)
+    { this.driver_class_loaded = dcl; }
+    
+    private void ensureDriverLoaded() throws SQLException
+    {
+        try
+        {
+            if (! isDriverClassLoaded())
+            {
+                if (driverClass != null)
+                    Class.forName( driverClass );
+                setDriverClassLoaded( true );
+            }
+        }
+        catch (ClassNotFoundException e)
+        {
+            if (logger.isLoggable(MLevel.WARNING))
+                logger.log(MLevel.WARNING, "Could not load driverClass " + driverClass, e);
+        }
+    }
+
+    // should NOT be sync'ed -- driver() is sync'ed and that's enough
+    // sync'ing the method creates the danger that one freeze on connect
+    // blocks access to the entire DataSource
+
+    public Connection getConnection() throws SQLException
+    { 
+        ensureDriverLoaded();
+
+        waitNoFreezeFile();
+
+        Connection out = driver().connect( jdbcUrl, properties ); 
+        if (out == null)
+            throw new SQLException("Apparently, jdbc URL '" + jdbcUrl + "' is not valid for the underlying " +
+                            "driver [" + driver() + "].");
+        return out;
+    }
+
+    // should NOT be sync'ed -- driver() is sync'ed and that's enough
+    // sync'ing the method creates the danger that one freeze on connect
+    // blocks access to the entire DataSource
+
+    public Connection getConnection(String username, String password) throws SQLException
+    { 
+        ensureDriverLoaded();
+        
+        waitNoFreezeFile();
+
+        Connection out = driver().connect( jdbcUrl, overrideProps(username, password) );  
+        if (out == null)
+            throw new SQLException("Apparently, jdbc URL '" + jdbcUrl + "' is not valid for the underlying " +
+                            "driver [" + driver() + "].");
+        return out;
+    }
+
+    public PrintWriter getLogWriter() throws SQLException
+    { return DriverManager.getLogWriter(); }
+
+    public void setLogWriter(PrintWriter out) throws SQLException
+    { DriverManager.setLogWriter( out ); }
+
+    public int getLoginTimeout() throws SQLException
+    { return DriverManager.getLoginTimeout(); }
+
+    public void setLoginTimeout(int seconds) throws SQLException
+    { DriverManager.setLoginTimeout( seconds ); }
+
+    //overrides
+    public synchronized void setJdbcUrl(String jdbcUrl)
+    {
+        //System.err.println( "setJdbcUrl( " + jdbcUrl + " )");
+        //new Exception("DEBUG STACK TRACE").printStackTrace();
+        super.setJdbcUrl( jdbcUrl );
+        clearDriver();
+    }
+
+    //"virtual properties"
+    public synchronized void setUser(String user)
+    {
+        String oldUser = this.getUser();
+        if (! eqOrBothNull( user, oldUser ))
+        {
+            if (user != null)
+                properties.put( SqlUtils.DRIVER_MANAGER_USER_PROPERTY, user ); 
+            else
+                properties.remove( SqlUtils.DRIVER_MANAGER_USER_PROPERTY );
+
+            pcs.firePropertyChange("user", oldUser, user);
+        }
+    }
+
+    public synchronized String getUser()
+    {
+//      System.err.println("getUser() -- DriverManagerDataSource@" + System.identityHashCode( this ) + 
+//      " using Properties@" + System.identityHashCode( properties ));
+//      new Exception("STACK TRACE DUMP").printStackTrace();
+        return properties.getProperty( SqlUtils.DRIVER_MANAGER_USER_PROPERTY ); 
+    }
+
+    public synchronized void setPassword(String password)
+    {
+        String oldPass = this.getPassword();
+        if (! eqOrBothNull( password, oldPass ))
+        {
+            if (password != null)
+                properties.put( SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY, password ); 
+            else
+                properties.remove( SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY );
+
+            pcs.firePropertyChange("password", oldPass, password);
+        }
+    }
+
+    public synchronized String getPassword()
+    { return properties.getProperty( SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY ); }
+
+    private final Properties overrideProps(String user, String password)
+    {
+        Properties overriding = (Properties) properties.clone(); //we are relying on a defensive clone in our base class!!!
+
+        if (user != null)
+            overriding.put(SqlUtils.DRIVER_MANAGER_USER_PROPERTY, user);
+        else
+            overriding.remove(SqlUtils.DRIVER_MANAGER_USER_PROPERTY);
+
+        if (password != null)
+            overriding.put(SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY, password);
+        else
+            overriding.remove(SqlUtils.DRIVER_MANAGER_PASSWORD_PROPERTY);
+
+        return overriding;
+    }
+
+    private synchronized Driver driver() throws SQLException
+    {
+        //System.err.println( "driver() <-- " + this );
+        if (driver == null)
+            driver = DriverManager.getDriver( jdbcUrl );
+        return driver;
+    }
+
+    private synchronized void clearDriver()
+    { driver = null; }
+
+    private static boolean eqOrBothNull( Object a, Object b )
+    { return (a == b || (a != null && a.equals(b))); }
+
+    // serialization stuff -- set up bound/constrained property event handlers on deserialization
+    private static final long serialVersionUID = 1;
+    private static final short VERSION = 0x0001;
+
+    private void writeObject( ObjectOutputStream oos ) throws IOException
+    {
+        oos.writeShort( VERSION );
+    }
+
+    private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
+    {
+        short version = ois.readShort();
+        switch (version)
+        {
+        case VERSION:
+            setUpPropertyListeners();
+            break;
+        default:
+            throw new IOException("Unsupported Serialized Version: " + version);
+        }
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/JavaBeanRefTest.java b/src/classes/com/mchange/v2/c3p0/test/JavaBeanRefTest.java
new file mode 100644
index 0000000..14ae83a
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/JavaBeanRefTest.java
@@ -0,0 +1,51 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import javax.naming.*;
+import com.mchange.v2.naming.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v2.c3p0.impl.*;
+
+public final class JavaBeanRefTest
+{
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		ComboPooledDataSource cpds = new ComboPooledDataSource();
+		Reference ref = cpds.getReference();
+		ComboPooledDataSource cpdsJBOF = (ComboPooledDataSource) (new JavaBeanObjectFactory()).getObjectInstance( ref, null, null, null );
+		ComboPooledDataSource cpdsCJBOF = (ComboPooledDataSource) (new C3P0JavaBeanObjectFactory()).getObjectInstance( ref, null, null, null );
+		System.err.println( "cpds: " + cpds );
+		System.err.println( "cpdsJBOF: " + cpdsJBOF );
+		System.err.println( "cpdsCJBOF: " + cpdsCJBOF );
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    private JavaBeanRefTest()
+    {}
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/test/JndiBindTest.java b/src/classes/com/mchange/v2/c3p0/test/JndiBindTest.java
new file mode 100644
index 0000000..0f54fc6
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/JndiBindTest.java
@@ -0,0 +1,101 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import javax.naming.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+
+public final class JndiBindTest
+{
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		String driverClass = null;
+		String jdbc_url    = null;
+		String username    = null;
+		String password    = null;
+		String dmds_name   = null;
+		String cpds_name   = null;
+		String pbds_name   = null;
+
+		if (argv.length == 7)
+		    {
+			driverClass = argv[0];
+			jdbc_url   = argv[1];
+			username   = argv[2];
+			password   = argv[3];
+			dmds_name  = argv[4];
+			cpds_name = argv[5];
+			pbds_name = argv[6];
+		    }
+		else if (argv.length == 5)
+		    {
+			driverClass = argv[0];
+			jdbc_url   = argv[1];
+			username   = null;
+			password   = null;
+			dmds_name  = argv[2];
+			cpds_name = argv[3];
+			pbds_name = argv[4];
+		    }
+		else
+		    usage();
+
+		if (! jdbc_url.startsWith("jdbc:") )
+		    usage();
+
+		DataSource dmds = DriverManagerDataSourceFactory.create( driverClass,
+									 jdbc_url, 
+									 username, 
+									 password );
+		WrapperConnectionPoolDataSource cpds = new WrapperConnectionPoolDataSource();
+		cpds.setNestedDataSource(dmds);
+		DataSource pbds = PoolBackedDataSourceFactory.create( driverClass,
+								      jdbc_url, 
+								      username, 
+								      password );
+
+		InitialContext ctx = new InitialContext();
+		ctx.rebind( dmds_name , dmds );
+		System.out.println( "DriverManagerDataSource bounds as " + dmds_name );
+		ctx.rebind( cpds_name , cpds );
+		System.out.println( "ConnectionPoolDataSource bounds as " + cpds_name );
+		ctx.rebind( pbds_name , pbds );
+		System.out.println( "PoolDataSource bounds as " + pbds_name );
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    private static void usage()
+    {
+	System.err.println("java " + JndiBindTest.class.getName() + " \\");
+	System.err.println("\t<jdbc_driver_class> \\");
+	System.err.println("\t<jdbc_url> [<username> <password>] \\");
+	System.err.println("\t<dmds_name> <cpds_name> <pbds_name>" );
+	System.exit(-1);
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/JndiLookupTest.java b/src/classes/com/mchange/v2/c3p0/test/JndiLookupTest.java
new file mode 100644
index 0000000..8993aa5
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/JndiLookupTest.java
@@ -0,0 +1,75 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import javax.naming.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+
+public final class JndiLookupTest
+{
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		
+		String dmds_name  = null;
+		String cpds_name = null;
+		String pbds_name = null;
+
+		if (argv.length == 3)
+		    {
+			dmds_name = argv[0];
+			cpds_name = argv[1];
+			pbds_name = argv[2];
+		    }
+		else
+		    usage();
+
+		InitialContext ctx = new InitialContext();
+		DataSource dmds = (DataSource) ctx.lookup( dmds_name );
+		dmds.getConnection().close();
+		System.out.println( "DriverManagerDataSource " + dmds_name + 
+				    " sucessfully looked up and checked.");
+		ConnectionPoolDataSource cpds = (ConnectionPoolDataSource) ctx.lookup( cpds_name );
+		cpds.getPooledConnection().close();
+		System.out.println( "ConnectionPoolDataSource " + cpds_name + 
+				    " sucessfully looked up and checked.");
+		DataSource pbds = (DataSource) ctx.lookup( pbds_name );
+		pbds.getConnection().close();
+		System.out.println( "PoolBackedDataSource " + pbds_name + 
+				    " sucessfully looked up and checked.");
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    private static void usage()
+    {
+	System.err.println("java " + 
+			   JndiLookupTest.class.getName() + " \\");
+	System.err.println("\t<dmds_name> <wcpds_name> <wpbds_name>" );
+	System.exit(-1);
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/ListTablesTest.java b/src/classes/com/mchange/v2/c3p0/test/ListTablesTest.java
new file mode 100644
index 0000000..5d46c8b
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/ListTablesTest.java
@@ -0,0 +1,59 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.sql.*;
+import javax.sql.*;
+import javax.naming.*;
+import com.mchange.v1.db.sql.*;
+
+public final class ListTablesTest
+{
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		InitialContext ctx = new InitialContext();
+		DataSource ds = (DataSource) ctx.lookup(argv[0]);
+		System.err.println( ds.getClass() );
+		Connection con = null;
+		ResultSet  rs  = null;
+		try
+		    {
+			con = ds.getConnection();
+			DatabaseMetaData md = con.getMetaData();
+			rs = md.getTables( null, null, "%", null);
+			while (rs.next())
+			    System.out.println(rs.getString(3));
+		    }
+		finally
+		    {
+			ResultSetUtils.attemptClose( rs );
+			ConnectionUtils.attemptClose( con );
+		    }
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/LoadPoolBackedDataSource.java b/src/classes/com/mchange/v2/c3p0/test/LoadPoolBackedDataSource.java
new file mode 100644
index 0000000..65ec68e
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/LoadPoolBackedDataSource.java
@@ -0,0 +1,244 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v1.db.sql.*;
+import com.mchange.v2.c3p0.DriverManagerDataSource;
+
+public final class LoadPoolBackedDataSource
+{
+    final static int NUM_THREADS = 50;
+    final static int ITERATIONS_PER_THREAD = 1000;
+
+    static Random random = new Random();
+    static DataSource ds;
+
+    public static void main(String[] argv)
+    {
+        if (argv.length > 0)
+        {
+            System.err.println( LoadPoolBackedDataSource.class.getName() + 
+                                " now requires no args. Please set everything in standard c3p0 config files.");
+            return;                    
+        }
+        String jdbc_url = null;
+        String username = null;
+        String password = null;
+
+    /*       
+	if (argv.length == 3)
+	    {
+		jdbc_url = argv[0];
+		username = argv[1];
+		password = argv[2];
+	    }
+	else if (argv.length == 1)
+	    {
+		jdbc_url = argv[0];
+		username = null;
+		password = null;
+	    }
+	else
+	    usage();
+	
+	if (! jdbc_url.startsWith("jdbc:") )
+	    usage();
+*/	
+	
+	try
+	    {
+        //DataSource ds_unpooled = DataSources.unpooledDataSource(jdbc_url, username, password);
+        DataSource ds_unpooled = DataSources.unpooledDataSource();
+		ds = DataSources.pooledDataSource( ds_unpooled );
+
+		Connection con = null;
+		Statement stmt = null;
+
+		try
+		    {
+			con = ds.getConnection();
+			stmt = con.createStatement();
+			stmt.executeUpdate("CREATE TABLE testpbds ( a varchar(16), b varchar(16) )");
+			System.err.println( "LoadPoolBackedDataSource -- TEST SCHEMA CREATED" );
+		    }
+		catch (SQLException e)
+		    {
+			e.printStackTrace();
+			System.err.println("relation testpbds already exists, or something " +
+					   "bad happened.");
+		    }
+		finally
+		    {
+			StatementUtils.attemptClose( stmt );
+			ConnectionUtils.attemptClose( con );
+		    }
+
+		Thread[] threads = new Thread[NUM_THREADS];
+		for (int i = 0; i < NUM_THREADS; ++i)
+		    {
+			Thread t = new ChurnThread();
+			threads[i] = t;
+			t.start();
+			System.out.println("THREAD MADE [" + i + "]");
+			Thread.sleep(1000);
+		    }
+		for (int i = 0; i < NUM_THREADS; ++i)
+		    threads[i].join();
+		
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+	finally
+	    {
+		Connection con = null;
+		Statement stmt = null;
+
+		try
+		    {
+			con = ds.getConnection();
+			stmt = con.createStatement();
+			stmt.executeUpdate("DROP TABLE testpbds");
+			System.err.println( "LoadPoolBackedDataSource -- TEST SCHEMA DROPPED" );
+		    }
+		catch (Exception e)
+		    {
+			e.printStackTrace();
+		    }
+		finally
+		    {
+			StatementUtils.attemptClose( stmt );
+			ConnectionUtils.attemptClose( con );
+		    }
+	    }
+    }
+
+    static class ChurnThread extends Thread
+    {
+	public void run()
+	{
+	    try
+		{
+		    for( int i = 0; i < ITERATIONS_PER_THREAD; ++i )
+			{
+			    Connection con = null;
+			    try
+				{
+				    con = ds.getConnection();
+				    int select = random.nextInt(3);
+                    switch (select)
+                    {
+                    case 0:
+                        executeSelect( con );
+                        break;
+                    case 1:
+                        executeInsert( con );
+                        break;
+                    case 2:
+                        executeDelete( con );
+                        break;
+                    }
+				    PooledDataSource pds = (PooledDataSource) ds;
+				    System.out.println( pds.getNumConnectionsDefaultUser() );
+				    System.out.println( pds.getNumIdleConnectionsDefaultUser() );
+				    System.out.println( pds.getNumBusyConnectionsDefaultUser() );
+				    System.out.println( pds.getNumConnectionsAllUsers() );
+				}
+			    finally
+				{ ConnectionUtils.attemptClose( con ); }
+
+			    //Thread.sleep( random.nextInt( 1000 ) );
+			}
+		}
+	    catch (Exception e)
+		{ e.printStackTrace(); }
+	}
+    }
+
+    static void executeInsert(Connection con) throws SQLException
+    {
+	Statement stmt = null;
+	try
+	    {
+		stmt = con.createStatement();
+		stmt.executeUpdate("INSERT INTO testpbds VALUES ('" +
+				   random.nextInt() + "', '" +
+				   random.nextInt() + "')");
+		System.out.println("INSERTION");
+	    }
+	finally
+	    {
+		StatementUtils.attemptClose( stmt );
+	    }
+    }
+
+    static void executeDelete(Connection con) throws SQLException
+    {
+    Statement stmt = null;
+    try
+        {
+        stmt = con.createStatement();
+        stmt.executeUpdate("DELETE FROM testpbds;");
+        System.out.println("DELETION");
+        }
+    finally
+        {
+        StatementUtils.attemptClose( stmt );
+        }
+    }
+
+    static void executeSelect(Connection con) throws SQLException
+    {
+	long l = System.currentTimeMillis();
+	Statement stmt = null;
+	ResultSet rs   = null;
+	try
+	    {
+		stmt = con.createStatement();
+		rs = stmt.executeQuery("SELECT count(*) FROM testpbds");
+		rs.next(); //we assume one row, one col
+		System.out.println("SELECT [count=" + rs.getInt(1) + ", time=" +
+				   (System.currentTimeMillis() - l) + " msecs]");
+	    }
+	finally
+	    {
+		ResultSetUtils.attemptClose( rs );
+		StatementUtils.attemptClose( stmt );
+	    }
+    }
+
+    private static void usage()
+    {
+	System.err.println("java " +
+			   "-Djdbc.drivers=<comma_sep_list_of_drivers> " +
+			   LoadPoolBackedDataSource.class.getName() +
+			   " <jdbc_url> [<username> <password>]" );
+	System.exit(-1);
+    }
+
+
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/OneThreadRepeatedInsertOrQueryTest.java b/src/classes/com/mchange/v2/c3p0/test/OneThreadRepeatedInsertOrQueryTest.java
new file mode 100644
index 0000000..edba820
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/OneThreadRepeatedInsertOrQueryTest.java
@@ -0,0 +1,163 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v1.db.sql.*;
+
+public final class OneThreadRepeatedInsertOrQueryTest
+{
+    final static String INSERT_STMT = "INSERT INTO testpbds VALUES ( ? , ? )";
+    final static String SELECT_STMT = "SELECT count(*) FROM testpbds";
+
+    static Random random = new Random();
+    static DataSource ds;
+
+    public static void main(String[] argv)
+    {
+	String jdbc_url = null;
+	String username = null;
+	String password = null;
+	if (argv.length == 3)
+	    {
+		jdbc_url = argv[0];
+		username = argv[1];
+		password = argv[2];
+	    }
+	else if (argv.length == 1)
+	    {
+		jdbc_url = argv[0];
+		username = null;
+		password = null;
+	    }
+	else
+	    usage();
+	
+	if (! jdbc_url.startsWith("jdbc:") )
+	    usage();
+	
+	
+	try
+	    {
+		DataSource ds_unpooled = DataSources.unpooledDataSource(jdbc_url, username, password);
+		ds = DataSources.pooledDataSource( ds_unpooled );
+
+		Connection con = null;
+		Statement stmt = null;
+
+		try
+		    {
+			con = ds.getConnection();
+			stmt = con.createStatement();
+			stmt.executeUpdate("CREATE TABLE testpbds ( a varchar(16), b varchar(16) )");
+		    }
+		catch (SQLException e)
+		    {
+			e.printStackTrace();
+			System.err.println("relation testpbds already exists, or something " +
+					   "bad happened.");
+		    }
+		finally
+		    {
+			StatementUtils.attemptClose( stmt );
+			ConnectionUtils.attemptClose( con );
+		    }
+
+		while(true)
+		    {
+			con = null;
+			try
+			    {
+				con = ds.getConnection();
+				boolean select = random.nextBoolean();
+				if (select)
+				    executeSelect( con );
+				else
+				    executeInsert( con );
+			    }
+			catch (Exception e)
+			    { e.printStackTrace(); }
+			finally
+			    { ConnectionUtils.attemptClose( con ); }
+			
+			//Thread.sleep( random.nextInt( 1000 ) );
+		    }
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    static void executeInsert(Connection con) throws SQLException
+    {
+	PreparedStatement pstmt = null;
+	try
+	    {
+		pstmt = con.prepareStatement(INSERT_STMT);
+		pstmt.setInt(1, random.nextInt());
+		pstmt.setInt(2, random.nextInt());
+		pstmt.executeUpdate();
+		System.out.println("INSERTION");
+	    }
+	finally
+	    {
+		// make sure forgetting this doesn't starve
+		// statement cache, as long as the connection
+		// closes...
+
+		StatementUtils.attemptClose( pstmt );
+	    }
+    }
+
+    static void executeSelect(Connection con) throws SQLException
+    {
+	long l = System.currentTimeMillis();
+	PreparedStatement pstmt = null;
+	ResultSet rs   = null;
+	try
+	    {
+		pstmt = con.prepareStatement(SELECT_STMT);
+		rs = pstmt.executeQuery();
+		rs.next(); //we assume one row, one col
+		System.out.println("SELECT [count=" + rs.getInt(1) + ", time=" +
+				   (System.currentTimeMillis() - l) + " msecs]");
+	    }
+	finally
+	    {
+		ResultSetUtils.attemptClose( rs );
+		StatementUtils.attemptClose( pstmt );
+	    }
+    }
+
+    private static void usage()
+    {
+	System.err.println("java " +
+			   "-Djdbc.drivers=<comma_sep_list_of_drivers> " +
+			   OneThreadRepeatedInsertOrQueryTest.class.getName() +
+			   " <jdbc_url> [<username> <password>]" );
+	System.exit(-1);
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/PSLoadPoolBackedDataSource.java b/src/classes/com/mchange/v2/c3p0/test/PSLoadPoolBackedDataSource.java
new file mode 100644
index 0000000..41f53d2
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/PSLoadPoolBackedDataSource.java
@@ -0,0 +1,226 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v1.db.sql.*;
+
+public final class PSLoadPoolBackedDataSource
+{
+    final static String INSERT_STMT = "INSERT INTO testpbds VALUES ( ? , ? )";
+    final static String SELECT_STMT = "SELECT count(*) FROM testpbds";
+    final static String DELETE_STMT = "DELETE FROM testpbds";
+
+    static Random random = new Random();
+    static DataSource ds;
+
+    public static void main(String[] argv)
+    {
+        if (argv.length > 0)
+        {
+            System.err.println( PSLoadPoolBackedDataSource.class.getName() + 
+                                " now requires no args. Please set everything in standard c3p0 config files.");
+            return;                    
+        }
+
+        String jdbc_url = null;
+        String username = null;
+        String password = null;
+
+        /*
+	if (argv.length == 3)
+	    {
+		jdbc_url = argv[0];
+		username = argv[1];
+		password = argv[2];
+	    }
+	else if (argv.length == 1)
+	    {
+		jdbc_url = argv[0];
+		username = null;
+		password = null;
+	    }
+	else
+	    usage();
+	
+	if (! jdbc_url.startsWith("jdbc:") )
+	    usage();
+	   */
+	
+	try
+	    {
+        //DataSource ds_unpooled = DataSources.unpooledDataSource(jdbc_url, username, password);
+        //DataSource ds_unpooled = new FreezableDriverManagerDataSource();
+
+        DataSource ds_unpooled = DataSources.unpooledDataSource();
+		ds = DataSources.pooledDataSource( ds_unpooled );
+
+		//new java.io.BufferedReader(new java.io.InputStreamReader(System.in)).readLine();
+
+		Connection con = null;
+		Statement stmt = null;
+
+		try
+		    {
+			con = ds_unpooled.getConnection();
+			stmt = con.createStatement();
+			stmt.executeUpdate("CREATE TABLE testpbds ( a varchar(16), b varchar(16) )");
+		    }
+		catch (SQLException e)
+		    {
+			e.printStackTrace();
+			System.err.println("relation testpbds already exists, or something " +
+					   "bad happened.");
+		    }
+		finally
+		    {
+			StatementUtils.attemptClose( stmt );
+			ConnectionUtils.attemptClose( con );
+		    }
+
+		//for (int i = 0; i < 5; ++i)
+		for (int i = 0; i < 50; ++i)
+		    {
+			Thread t = new ChurnThread();
+			t.start();
+			System.out.println("THREAD MADE [" + i + "]");
+			Thread.sleep(1000);
+		    }
+		
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    static class ChurnThread extends Thread
+    {
+	public void run()
+	{
+	    try
+		{
+		    while(true)
+			{
+			    Connection con = null;
+			    try
+				{
+				    con = ds.getConnection();
+				    int select = random.nextInt(3);
+                    switch (select)
+                    {
+                    case 0:
+                        executeSelect( con );
+                        break;
+                    case 1:
+                        executeInsert( con );
+                        break;
+                    case 2:
+                        executeDelete( con );
+                        break;
+                    }
+				}
+ 			    catch (Exception e)
+ 				{ e.printStackTrace(); }
+			    finally
+				{ ConnectionUtils.attemptClose( con ); }
+
+			    //Thread.sleep( random.nextInt( 1000 ) );
+			}
+		}
+	    catch (Exception e)
+		{ e.printStackTrace(); }
+	}
+    }
+
+    static void executeInsert(Connection con) throws SQLException
+    {
+	PreparedStatement pstmt = null;
+	try
+	    {
+		pstmt = con.prepareStatement(INSERT_STMT);
+		pstmt.setInt(1, random.nextInt());
+		pstmt.setInt(2, random.nextInt());
+		pstmt.executeUpdate();
+		System.out.println("INSERTION");
+	    }
+	finally
+	    {
+		// make sure forgetting this doesn't starve
+		// statement cache, as long as the connection
+		// closes...
+
+		StatementUtils.attemptClose( pstmt );
+	    }
+    }
+
+    static void executeSelect(Connection con) throws SQLException
+    {
+	long l = System.currentTimeMillis();
+	PreparedStatement pstmt = null;
+	ResultSet rs   = null;
+	try
+	    {
+		pstmt = con.prepareStatement(SELECT_STMT);
+		rs = pstmt.executeQuery();
+		rs.next(); //we assume one row, one col
+		System.out.println("SELECT [count=" + rs.getInt(1) + ", time=" +
+				   (System.currentTimeMillis() - l) + " msecs]");
+	    }
+	finally
+	    {
+		ResultSetUtils.attemptClose( rs );
+		StatementUtils.attemptClose( pstmt );
+	    }
+    }
+
+    static void executeDelete(Connection con) throws SQLException
+    {
+    PreparedStatement pstmt = null;
+    ResultSet rs   = null;
+    try
+        {
+        pstmt = con.prepareStatement(DELETE_STMT);
+        int deleted = pstmt.executeUpdate();
+        System.out.println("DELETE [" + deleted + " rows]");
+        }
+    finally
+        {
+        ResultSetUtils.attemptClose( rs );
+        StatementUtils.attemptClose( pstmt );
+        }
+    }
+    
+    /*
+    private static void usage()
+    {
+	System.err.println("java " +
+			   "-Djdbc.drivers=<comma_sep_list_of_drivers> " +
+			   PSLoadPoolBackedDataSource.class.getName() +
+			   " <jdbc_url> [<username> <password>]" );
+	System.exit(-1);
+    }
+    */
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/ProxyWrappersTest.java b/src/classes/com/mchange/v2/c3p0/test/ProxyWrappersTest.java
new file mode 100644
index 0000000..b145ec4
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/ProxyWrappersTest.java
@@ -0,0 +1,73 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v1.db.sql.*;
+
+public final class ProxyWrappersTest
+{
+    public static void main(String[] argv)
+    {
+	ComboPooledDataSource cpds = null;
+	Connection c               = null;
+	try
+	    {
+		cpds = new ComboPooledDataSource();
+		cpds.setDriverClass( "org.postgresql.Driver" );
+		cpds.setJdbcUrl( "jdbc:postgresql://localhost/c3p0-test" );
+		cpds.setUser("swaldman");
+		cpds.setPassword("test");
+		cpds.setMinPoolSize(5);
+		cpds.setAcquireIncrement(5);
+		cpds.setMaxPoolSize(20);
+
+		c = cpds.getConnection();
+		c.setAutoCommit( false );
+		Statement stmt = c.createStatement();
+		stmt.executeUpdate("CREATE TABLE pwtest_table (col1 char(5), col2 char(5))");
+		ResultSet rs = stmt.executeQuery("SELECT * FROM pwtest_table");
+		System.err.println("rs: " + rs);
+		System.err.println("rs.getStatement(): " + rs.getStatement());
+		System.err.println("rs.getStatement().getConnection(): " + rs.getStatement().getConnection());
+	    }
+	catch( Exception e )
+	    { e.printStackTrace(); }
+	finally
+	    {
+		try { if (c!= null) c.rollback(); }
+		catch (Exception e) { e.printStackTrace(); }
+		try { if (cpds!= null) cpds.close(); }
+		catch (Exception e) { e.printStackTrace(); }
+	    }
+    }
+}
+
+
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/test/RawConnectionOpTest.java b/src/classes/com/mchange/v2/c3p0/test/RawConnectionOpTest.java
new file mode 100644
index 0000000..6c52b89
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/RawConnectionOpTest.java
@@ -0,0 +1,108 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.lang.reflect.Method;
+
+import com.mchange.v2.c3p0.ComboPooledDataSource;
+import com.mchange.v2.c3p0.C3P0ProxyConnection;
+import com.mchange.v2.c3p0.C3P0ProxyStatement;
+import com.mchange.v2.c3p0.util.TestUtils;
+
+public final class RawConnectionOpTest
+{
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		String jdbc_url    = null;
+		String username    = null;
+		String password    = null;
+		
+		if (argv.length == 3)
+		    {
+			jdbc_url   = argv[0];
+			username   = argv[1];
+			password   = argv[2];
+		    }
+		else if (argv.length == 1)
+		    {
+			jdbc_url   = argv[0];
+			username   = null;
+			password   = null;
+		    }
+		else
+		    usage();
+		
+		if (! jdbc_url.startsWith("jdbc:") )
+		    usage();
+				    
+		ComboPooledDataSource cpds = new ComboPooledDataSource();
+		cpds.setJdbcUrl( jdbc_url );
+		cpds.setUser( username );
+		cpds.setPassword( password );
+  		cpds.setMaxPoolSize( 10 );
+//  		cpds.setUsesTraditionalReflectiveProxies( true );
+				
+		C3P0ProxyConnection conn = (C3P0ProxyConnection) cpds.getConnection();
+		Method toStringMethod = Object.class.getMethod("toString", new Class[]{});
+		Method identityHashCodeMethod = System.class.getMethod("identityHashCode", new Class[] {Object.class});
+		System.out.println("rawConnection.toString() -> " + 
+				   conn.rawConnectionOperation(toStringMethod, C3P0ProxyConnection.RAW_CONNECTION, new Object[]{}));
+		Integer ihc = (Integer) conn.rawConnectionOperation(identityHashCodeMethod, null, new Object[]{C3P0ProxyConnection.RAW_CONNECTION});
+		System.out.println("System.identityHashCode( rawConnection ) -> " + Integer.toHexString( ihc.intValue() ));
+
+		C3P0ProxyStatement stmt = (C3P0ProxyStatement) conn.createStatement();
+		System.out.println("rawStatement.toString() -> " + 
+				   stmt.rawStatementOperation(toStringMethod, C3P0ProxyStatement.RAW_STATEMENT, new Object[]{}));
+		Integer ihc2 = (Integer) stmt.rawStatementOperation(identityHashCodeMethod, null, new Object[]{C3P0ProxyStatement.RAW_STATEMENT});
+		System.out.println("System.identityHashCode( rawStatement ) -> " + Integer.toHexString( ihc2.intValue() ));
+
+		conn.close();	
+
+  		for (int i = 0; i < 10; ++i)
+  		    {
+  			C3P0ProxyConnection check = null;
+  			try
+  			    {
+  				check = (C3P0ProxyConnection) cpds.getConnection();
+  				//System.err.println( TestUtils.samePhysicalConnection( conn, check ) );
+  				System.err.println( TestUtils.physicalConnectionIdentityHashCode( check ) == ihc.intValue() );
+  			    }
+  			finally
+  			    { /* if (check != null) check.close(); */ }
+  		    }
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    private static void usage()
+    {
+	System.err.println("java " + RawConnectionOpTest.class.getName() + " \\");
+	System.err.println("\t<jdbc_driver_class> \\");
+	System.err.println("\t<jdbc_url> [<username> <password>]");
+	System.exit(-1);
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/StatsTest.java b/src/classes/com/mchange/v2/c3p0/test/StatsTest.java
new file mode 100644
index 0000000..c215e3a
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/StatsTest.java
@@ -0,0 +1,112 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v1.db.sql.*;
+
+public final class StatsTest
+{
+    static void display( ComboPooledDataSource cpds ) throws Exception
+    {
+	System.err.println("numConnections: " + cpds.getNumConnections());
+	System.err.println("numBusyConnections: " + cpds.getNumBusyConnections());
+	System.err.println("numIdleConnections: " + cpds.getNumIdleConnections());
+	System.err.println("numUnclosedOrphanedConnections: " + cpds.getNumUnclosedOrphanedConnections());
+	System.err.println();
+    }
+
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		ComboPooledDataSource cpds = new ComboPooledDataSource();
+		cpds.setJdbcUrl( argv[0] );
+		cpds.setUser( argv[1] );
+		cpds.setPassword( argv[2] );
+		cpds.setMinPoolSize(5);
+		cpds.setAcquireIncrement(5);
+		cpds.setMaxPoolSize(20);
+
+		System.err.println("Initial...");
+		display( cpds );
+		Thread.sleep(2000);
+
+ 		HashSet hs = new HashSet();
+ 		for (int i = 0; i < 20; ++i)
+ 		    {
+			Connection c = cpds.getConnection();
+ 			hs.add( c );
+			System.err.println( "Adding (" + (i + 1) + ") " + c );
+ 			display( cpds );
+			Thread.sleep(1000);
+
+// 			if (i == 9)
+// 			    {
+//  				//System.err.println("hardReset()ing");
+//  				//cpds.hardReset();
+// 				System.err.println("softReset()ing");
+// 				cpds.softReset();
+// 			    }
+ 		    }
+		
+		int count = 0;
+		for (Iterator ii = hs.iterator(); ii.hasNext(); )
+		    {
+			Connection c = ((Connection) ii.next());
+			System.err.println("Removing " + ++count);
+			ii.remove();
+			try { c.getMetaData().getTables( null, null, "PROBABLYNOT", new String[] {"TABLE"} ); }
+			catch (Exception e)
+			    { 
+				System.err.println( e ); 
+				System.err.println();
+				continue;
+			    }
+			finally
+			    { c.close(); }
+			display( cpds );
+		    }
+
+		System.err.println("Closing data source, \"forcing\" garbage collection, and sleeping for 5 seconds...");
+		cpds.close();
+		System.gc();
+		System.err.println("Main Thread: Sleeping for five seconds!");
+		Thread.sleep(5000);
+// 		System.gc();
+// 		Thread.sleep(5000);
+		System.err.println("Bye!");
+	    }
+	catch( Exception e )
+	    { e.printStackTrace(); }
+    }
+}
+
+
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/test/TestConnectionCustomizer.java b/src/classes/com/mchange/v2/c3p0/test/TestConnectionCustomizer.java
new file mode 100644
index 0000000..3cf1d49
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/TestConnectionCustomizer.java
@@ -0,0 +1,42 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import com.mchange.v2.c3p0.*;
+import java.sql.Connection;
+
+public class TestConnectionCustomizer extends AbstractConnectionCustomizer
+{
+    public void onAcquire( Connection c, String pdsIdt )
+    { System.err.println("Acquired " + c + " [" + pdsIdt + "]"); }
+
+    public void onDestroy( Connection c, String pdsIdt )
+    { System.err.println("Destroying " + c + " [" + pdsIdt + "]"); }
+
+    public void onCheckOut( Connection c, String pdsIdt )
+    { System.err.println("Checked out " + c + " [" + pdsIdt + "]"); }
+
+    public void onCheckIn( Connection c, String pdsIdt )
+    { System.err.println("Checking in " + c + " [" + pdsIdt + "]"); }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/TestRefSerStuff.java b/src/classes/com/mchange/v2/c3p0/test/TestRefSerStuff.java
new file mode 100644
index 0000000..a6d35d3
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/TestRefSerStuff.java
@@ -0,0 +1,185 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test;
+
+import java.sql.*;
+import javax.sql.*;
+import com.mchange.v2.c3p0.*;
+import com.mchange.v1.db.sql.*;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import com.mchange.v2.naming.ReferenceableUtils;
+import com.mchange.v2.ser.SerializableUtils;
+import com.mchange.v2.c3p0.DriverManagerDataSource;
+import com.mchange.v2.c3p0.PoolBackedDataSource;
+
+
+public final class TestRefSerStuff
+{
+    static void create(DataSource ds) throws SQLException
+    {
+	Connection con = null;
+	Statement stmt = null;
+	try
+	    {
+		con = ds.getConnection();
+		stmt = con.createStatement();
+		stmt.executeUpdate("CREATE TABLE TRSS_TABLE ( a_col VARCHAR(16) )");
+	    }
+	finally
+	    {
+		StatementUtils.attemptClose( stmt );
+		ConnectionUtils.attemptClose( con );
+	    }
+    }
+
+    static void drop(DataSource ds) throws SQLException
+    {
+	Connection con = null;
+	Statement stmt = null;
+	try
+	    {
+		con = ds.getConnection();
+		stmt = con.createStatement();
+		stmt.executeUpdate("DROP TABLE TRSS_TABLE");
+	    }
+	finally
+	    {
+		StatementUtils.attemptClose( stmt );
+		ConnectionUtils.attemptClose( con );
+	    }
+    }
+
+    static void doSomething(DataSource ds) throws SQLException
+    {
+	Connection con = null;
+	Statement stmt = null;
+	try
+	    {
+		con = ds.getConnection();
+		stmt = con.createStatement();
+		int i = stmt.executeUpdate("INSERT INTO TRSS_TABLE VALUES ('" + 
+					   System.currentTimeMillis() + "')");
+		if (i != 1)
+		    throw new SQLException("Insert failed somehow strange!");
+	    }
+	finally
+	    {
+		StatementUtils.attemptClose( stmt );
+		ConnectionUtils.attemptClose( con );
+	    }
+    }
+
+    /*
+    private static void usage()
+    {
+	System.err.println("java " +
+			   "-Djdbc.drivers=<comma_sep_list_of_drivers> " +
+			   TestRefSerStuff.class.getName() +
+			   " <jdbc_url> [<username> <password>]" );
+	System.exit(-1);
+    }
+    */
+
+    static void doTest(DataSource checkMe) throws Exception
+    {
+	doSomething( checkMe );
+	System.err.println("\tcreated:   " + checkMe);
+	DataSource afterSer = (DataSource) SerializableUtils.testSerializeDeserialize( checkMe );
+	doSomething( afterSer );
+	System.err.println("\tafter ser: " + afterSer );
+	Reference ref = ((Referenceable) checkMe).getReference();
+//  		    System.err.println("ref: " + ref);
+//  		    System.err.println("Factory Class: " + ref.getFactoryClassName());
+	DataSource afterRef = (DataSource) ReferenceableUtils.referenceToObject( ref, 
+										 null, 
+										 null, 
+										 null );
+//  		    System.err.println("afterRef data source: " + afterRef);
+	doSomething( afterRef );
+	System.err.println("\tafter ref: " + afterRef );
+    }
+
+    public static void main( String[] argv )
+    {
+        if (argv.length > 0)
+        {
+            System.err.println( TestRefSerStuff.class.getName() + 
+                                " now requires no args. Please set everything in standard c3p0 config files.");
+            return;                    
+        }
+
+        /*
+	String jdbcUrl = null;
+	String username = null;
+	String password = null;
+	if (argv.length == 3)
+	    {
+		jdbcUrl = argv[0];
+		username = argv[1];
+		password = argv[2];
+	    }
+	else if (argv.length == 1)
+	    {
+		jdbcUrl = argv[0];
+		username = null;
+		password = null;
+	    }
+	else
+	    usage();
+	
+	if (! jdbcUrl.startsWith("jdbc:") )
+	    usage();
+	*/
+	
+	try
+	    {
+		DriverManagerDataSource dmds = new DriverManagerDataSource();
+		//dmds.setJdbcUrl( jdbcUrl );
+		//dmds.setUser( username );
+		//dmds.setPassword( password );
+		try { drop( dmds ); }
+		catch (Exception e)
+		    { /* Ignore */ }
+		create( dmds );
+
+		System.err.println("DriverManagerDataSource:");
+		doTest( dmds );
+		
+		WrapperConnectionPoolDataSource wcpds = new WrapperConnectionPoolDataSource();
+		wcpds.setNestedDataSource( dmds );
+		PoolBackedDataSource pbds = new PoolBackedDataSource();
+		pbds.setConnectionPoolDataSource( wcpds );
+		
+		System.err.println("PoolBackedDataSource:");
+		doTest( pbds );
+        
+        ComboPooledDataSource cpds = new ComboPooledDataSource();
+        doTest( cpds );
+	    }
+	catch ( Exception e )
+	    { e.printStackTrace(); }
+    }
+
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/junit/C3P0JUnitTestCaseBase.java b/src/classes/com/mchange/v2/c3p0/test/junit/C3P0JUnitTestCaseBase.java
new file mode 100644
index 0000000..db49e59
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/junit/C3P0JUnitTestCaseBase.java
@@ -0,0 +1,62 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test.junit;
+
+import com.mchange.v2.c3p0.*;
+import junit.framework.TestCase;
+
+public abstract class C3P0JUnitTestCaseBase extends TestCase
+{
+    protected ComboPooledDataSource cpds;
+
+    protected void setUp() 
+    {
+        //we let this stuff get setup in c3p0.properties now
+        
+        /*
+	String url      = System.getProperty("c3p0.test.jdbc.url");
+	String user     = System.getProperty("c3p0.test.jdbc.user");
+	String password = System.getProperty("c3p0.test.jdbc.password");
+         */
+
+	//C3P0JUnitTestConfig.loadDrivers();
+	cpds = new ComboPooledDataSource();
+    
+    /*
+	cpds.setJdbcUrl( url );
+	cpds.setUser( user );
+	cpds.setPassword( password );
+    */
+    }
+
+    protected void tearDown() 
+    { 
+	try { cpds.close(); }
+	catch ( Exception e )
+	    {
+		System.err.println("Exception on DataSource close in JUnit test tearDown():");
+		e.printStackTrace(); 
+	    }
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/test/junit/ConnectionPropertiesResetJUnitTestCase.java b/src/classes/com/mchange/v2/c3p0/test/junit/ConnectionPropertiesResetJUnitTestCase.java
new file mode 100644
index 0000000..b3d7895
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/junit/ConnectionPropertiesResetJUnitTestCase.java
@@ -0,0 +1,106 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test.junit;
+
+import java.sql.*;
+import java.util.*;
+import junit.framework.*;
+
+public final class ConnectionPropertiesResetJUnitTestCase extends C3P0JUnitTestCaseBase
+{
+    final static Map TM;
+
+    static
+    {
+	Map tmp = new HashMap();
+	tmp.put("FAKE", SQLData.class);
+	TM = Collections.unmodifiableMap( tmp );
+    }
+
+    public void testAllConnectionDefaultsReset()
+    {
+// 	System.err.println("XOXO err");
+// 	System.out.println("XOXO out");
+
+	cpds.setInitialPoolSize(5);
+	cpds.setMinPoolSize(5);
+	cpds.setMaxPoolSize(5);
+	cpds.setMaxIdleTime(0);
+	cpds.setTestConnectionOnCheckout(false);
+	cpds.setTestConnectionOnCheckin(false);
+	cpds.setIdleConnectionTestPeriod(0);
+
+	String dfltCat;
+	int dflt_txn_isolation;
+
+	try
+	    {
+		Connection con = null;
+		try
+		    {
+			con = cpds.getConnection();
+			
+
+			dfltCat = con.getCatalog();
+			dflt_txn_isolation = con.getTransactionIsolation();
+
+			try { con.setReadOnly(true); } catch (Exception e) { /* setReadOnly() not supported */ }
+			try { con.setTypeMap(TM); } catch (Exception e) { /* setTypeMap() not supported */ }
+			try { con.setCatalog("C3P0TestCatalogXXX"); } catch (Exception e) { /* setCatalog() not supported */ }
+			try 
+			    { 
+				con.setTransactionIsolation( dflt_txn_isolation == Connection.TRANSACTION_SERIALIZABLE ? 
+							     Connection.TRANSACTION_READ_COMMITTED : 
+							     Connection.TRANSACTION_SERIALIZABLE ); 
+			    } 
+			catch (Exception e) { /* setTransactionIsolation() not fully supported */ }
+		    }
+		finally
+		    { 
+			try { if (con != null) con.close(); }
+			catch (Exception e) {}
+		    }
+
+		Connection[] cons = new Connection[5];
+		for (int i = 0; i < 5; ++i)
+		    {
+			cons[i] = cpds.getConnection();
+			assertFalse( "Connection from pool should not be readOnly!", cons[i].isReadOnly() );
+
+			// some drivers return null rather than an empty type map
+			Map typeMap = cons[i].getTypeMap();
+			assertTrue( "Connection from pool should have an empty type map!", (typeMap == null ? true : typeMap.isEmpty() ) ); 
+
+			assertEquals( "Connection from pool should have default catalog set!", dfltCat, cons[i].getCatalog() );
+			assertEquals( "Connection from pool should have default txn isolation set!", dflt_txn_isolation, cons[i].getTransactionIsolation() );
+			cons[i].close();
+		    }
+	    }
+	catch (Exception e)
+	    {
+		e.printStackTrace();
+		fail( e.getMessage() );
+	    }
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/c3p0/test/junit/MarshallUnmarshallDataSourcesJUnitTestCase.java b/src/classes/com/mchange/v2/c3p0/test/junit/MarshallUnmarshallDataSourcesJUnitTestCase.java
new file mode 100644
index 0000000..f473896
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/test/junit/MarshallUnmarshallDataSourcesJUnitTestCase.java
@@ -0,0 +1,111 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.test.junit;
+
+import java.util.*;
+import javax.naming.Reference;
+import junit.framework.*;
+import com.mchange.v2.ser.SerializableUtils;
+import com.mchange.v2.beans.BeansUtils;
+import com.mchange.v2.naming.ReferenceableUtils;
+import com.mchange.v2.c3p0.ComboPooledDataSource;
+
+public final class MarshallUnmarshallDataSourcesJUnitTestCase extends C3P0JUnitTestCaseBase
+{
+    final static Collection EXCLUDE_PROPS = Arrays.asList( new String[]{
+                                    "allUsers",
+                                    "connection",
+                                    "connectionPoolDataSource",
+                                    "effectivePropertyCycleDefaultUser",
+                                    "lastCheckinFailureDefaultUser",
+                                    "lastCheckoutFailureDefaultUser",
+                                    "lastConnectionTestFailureDefaultUser",
+                                    "lastIdleTestFailureDefaultUser",
+									"logWriter",
+									"numBusyConnections",
+									"numBusyConnectionsAllUsers",
+									"numBusyConnectionsDefaultUser",
+									"numConnections",
+									"numConnectionsAllUsers",
+									"numConnectionsDefaultUser",
+                                    "numFailedCheckinsDefaultUser",
+                                    "numFailedCheckoutsDefaultUser",
+                                    "numFailedIdleTestsDefaultUser",
+									"numIdleConnections",
+									"numIdleConnectionsAllUsers",
+									"numIdleConnectionsDefaultUser",
+									"numUnclosedOrphanedConnections",
+									"numUnclosedOrphanedConnectionsAllUsers",
+									"numUnclosedOrphanedConnectionsDefaultUser",
+									"numUserPools",
+                                    "startTimeMillisDefaultUser",
+                                    "statementCacheNumCheckedOutDefaultUser",
+                                    "statementCacheNumCheckedOutStatementsAllUsers",
+                                    "statementCacheNumConnectionsWithCachedStatementsAllUsers",
+                                    "statementCacheNumConnectionsWithCachedStatementsDefaultUser",
+                                    "statementCacheNumStatementsAllUsers",
+                                    "statementCacheNumStatementsDefaultUser",
+                                    "threadPoolSize",
+                                    "threadPoolNumActiveThreads",
+                                    "threadPoolNumIdleThreads",
+                                    "threadPoolNumTasksPending",
+                                    "threadPoolStackTraces",
+                                    "threadPoolStatus",
+                                    "upTimeMillisDefaultUser"
+                                    } );
+
+    public void testSerializationRoundTrip()
+    {
+	try
+	    {
+        cpds.setIdentityToken("poop"); //simulate a never-before-seen data source, so it's a new registration on deserialization
+		byte[] pickled = SerializableUtils.toByteArray(cpds);
+		ComboPooledDataSource unpickled = (ComboPooledDataSource) SerializableUtils.fromByteArray( pickled );
+		assertTrue( "Marshalled and unmarshalled DataSources should have the same properties!", 
+			    BeansUtils.equalsByAccessibleProperties( cpds, unpickled, EXCLUDE_PROPS ) );
+	    }
+	catch (Exception e)
+	    {
+		e.printStackTrace();
+		fail( e.getMessage() );
+	    }
+    }
+
+    public void testRefDerefRoundTrip()
+    {
+	try
+	    {
+        cpds.setIdentityToken("scoop"); //simulate a never-before-seen data source, so it's a new registration on deserialization
+		Reference ref = cpds.getReference();
+		ComboPooledDataSource unpickled = (ComboPooledDataSource) ReferenceableUtils.referenceToObject( ref, null, null, null );
+		assertTrue( "Marshalled and unmarshalled DataSources should have the same properties!", 
+			    BeansUtils.equalsByAccessibleProperties( cpds, unpickled, EXCLUDE_PROPS ) );
+	    }
+	catch (Exception e)
+	    {
+		e.printStackTrace();
+		fail( e.getMessage() );
+	    }
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/util/CloseReportingConnectionWrapper.java b/src/classes/com/mchange/v2/c3p0/util/CloseReportingConnectionWrapper.java
new file mode 100644
index 0000000..7f93396
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/util/CloseReportingConnectionWrapper.java
@@ -0,0 +1,40 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.util;
+
+import java.sql.*;
+import com.mchange.v2.sql.filter.*;
+
+public class CloseReportingConnectionWrapper extends FilterConnection
+{
+    public CloseReportingConnectionWrapper( Connection conn )
+    { super( conn ); }
+
+    public void close() throws SQLException
+    {
+	//System.err.print("ADRIAN -- ");
+	new SQLWarning("Connection.close() called!").printStackTrace();
+	super.close();
+    }
+}
diff --git a/src/classes/com/mchange/v2/c3p0/util/ConnectionEventSupport.java b/src/classes/com/mchange/v2/c3p0/util/ConnectionEventSupport.java
new file mode 100644
index 0000000..e5bd165
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/util/ConnectionEventSupport.java
@@ -0,0 +1,76 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.util;
+
+import java.util.*;
+import java.sql.*;
+import javax.sql.*;
+
+public class ConnectionEventSupport
+{
+    PooledConnection source;
+    HashSet          mlisteners = new HashSet();
+
+    public ConnectionEventSupport(PooledConnection source)
+    { this.source = source; }
+
+    public synchronized void addConnectionEventListener(ConnectionEventListener mlistener)
+    {mlisteners.add(mlistener);}
+
+    public synchronized void removeConnectionEventListener(ConnectionEventListener mlistener)
+    {mlisteners.remove(mlistener);}
+
+    public void fireConnectionClosed()
+    {
+	Set mlCopy;
+
+	synchronized (this)
+	    { mlCopy = (Set) mlisteners.clone(); }
+
+	ConnectionEvent evt = new ConnectionEvent(source);
+	for (Iterator i = mlCopy.iterator(); i.hasNext();)
+	    {
+		ConnectionEventListener cl = (ConnectionEventListener) i.next();
+		cl.connectionClosed(evt);
+	    }
+    }
+
+    public void fireConnectionErrorOccurred(SQLException error)
+    {
+	Set mlCopy;
+
+	synchronized (this)
+	    { mlCopy = (Set) mlisteners.clone(); }
+
+	ConnectionEvent evt = new ConnectionEvent(source, error);
+	for (Iterator i = mlCopy.iterator(); i.hasNext();)
+	    {
+		ConnectionEventListener cl = (ConnectionEventListener) i.next();
+		cl.connectionErrorOccurred(evt);
+	    }
+    }
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/c3p0/util/TestUtils.java b/src/classes/com/mchange/v2/c3p0/util/TestUtils.java
new file mode 100644
index 0000000..dac0bb1
--- /dev/null
+++ b/src/classes/com/mchange/v2/c3p0/util/TestUtils.java
@@ -0,0 +1,182 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.c3p0.util;
+
+import java.sql.*;
+import javax.sql.*;
+import java.lang.reflect.*;
+import java.util.Random;
+import com.mchange.v2.sql.SqlUtils;
+import com.mchange.v2.c3p0.C3P0ProxyConnection;
+
+public final class TestUtils
+{
+    private final static Method OBJECT_EQUALS;
+    private final static Method IDENTITY_HASHCODE;
+    private final static Method IPCFP;
+
+
+    static
+    {
+	try
+	    { 
+		OBJECT_EQUALS = Object.class.getMethod("equals", new Class[] { Object.class }); 
+		IDENTITY_HASHCODE = System.class.getMethod("identityHashCode", new Class[] {Object.class});
+
+		// is this okay? getting a method from a class while it is still being initialized?
+		IPCFP = TestUtils.class.getMethod("isPhysicalConnectionForProxy", new Class[] {Connection.class, C3P0ProxyConnection.class});
+	    }
+	catch ( Exception e )
+	    {
+		e.printStackTrace();
+		throw new RuntimeException("Huh? Can't reflectively get ahold of expected methods?");
+	    }
+    }
+		
+    /**
+     * In general, if this method returns true for two distinct C3P0ProxyConnections, it indicates a c3p0 bug.
+     * Once a proxy Connection is close()ed, it should not permit any sort of operation. Prior to Connection
+     * close(), there should be at most one valid proxy Connection associated with a given physical Connection.
+     */
+    public static boolean samePhysicalConnection( C3P0ProxyConnection con1, C3P0ProxyConnection con2 ) throws SQLException
+    {
+	try 
+	    { 
+		Object out = con1.rawConnectionOperation( IPCFP, null, new Object[] { C3P0ProxyConnection.RAW_CONNECTION, con2 } ); 
+		return ((Boolean) out).booleanValue();
+	    }
+	catch (Exception e)
+	    {
+		e.printStackTrace();
+		throw SqlUtils.toSQLException( e );
+	    }
+    }
+
+    public static boolean isPhysicalConnectionForProxy( Connection physicalConnection, C3P0ProxyConnection proxy ) throws SQLException
+    {
+	try 
+	    { 
+		Object out = proxy.rawConnectionOperation( OBJECT_EQUALS, physicalConnection, new Object[] { C3P0ProxyConnection.RAW_CONNECTION } ); 
+		return ((Boolean) out).booleanValue();
+	    }
+	catch (Exception e)
+	    {
+		e.printStackTrace();
+		throw SqlUtils.toSQLException( e );
+	    }
+    }
+
+    public static int physicalConnectionIdentityHashCode( C3P0ProxyConnection conn ) throws SQLException
+    {
+	try 
+	    { 
+		Object out = conn.rawConnectionOperation( IDENTITY_HASHCODE, null, new Object[] { C3P0ProxyConnection.RAW_CONNECTION } ); 
+		return ((Integer) out).intValue();
+	    }
+	catch (Exception e)
+	    {
+		e.printStackTrace();
+		throw SqlUtils.toSQLException( e );
+	    }
+    }
+
+
+    public static DataSource unreliableCommitDataSource(DataSource ds) throws Exception
+    {
+	return (DataSource) Proxy.newProxyInstance( TestUtils.class.getClassLoader(),
+						    new Class[] { DataSource.class },
+						    new StupidDataSourceInvocationHandler( ds ) );
+    }
+
+    private TestUtils()
+    {}
+
+    static class StupidDataSourceInvocationHandler implements InvocationHandler
+    {
+	DataSource ds;
+	Random r = new Random();
+	
+	StupidDataSourceInvocationHandler(DataSource ds)
+	{ this.ds = ds; }
+	
+	public Object invoke(Object proxy, Method method, Object[] args)
+	    throws Throwable
+	{
+	    if ( "getConnection".equals(method.getName()) )
+		{
+		    Connection conn = (Connection) method.invoke( ds, args );
+		    return Proxy.newProxyInstance( TestUtils.class.getClassLoader(),
+						   new Class[] { Connection.class },
+						   new StupidConnectionInvocationHandler( conn ) );
+		}
+	    else
+		return method.invoke( ds, args );
+	}
+    }
+
+    static class StupidConnectionInvocationHandler implements InvocationHandler
+    {
+	Connection conn;
+	Random r = new Random();
+	
+	boolean invalid = false;
+
+	StupidConnectionInvocationHandler(Connection conn)
+	{ this.conn = conn; }
+	
+	public Object invoke(Object proxy, Method method, Object[] args)
+	    throws Throwable
+	{
+	    if ("close".equals(method.getName()))
+		{
+		    if (invalid)
+			{
+			    new Exception("Duplicate close() called on Connection!!!").printStackTrace();
+			}
+		    else
+			{
+			    //new Exception("CLOSE CALLED ON UNPOOLED DATASOURCE CONNECTION!").printStackTrace();
+			    invalid = true;
+			}
+		    return null;
+		}
+	    else if ( invalid )
+		throw new SQLException("Connection closed -- cannot " + method.getName());
+	    else if ( "commit".equals(method.getName())  && r.nextInt(100) == 0 )
+		{
+		    conn.rollback();
+		    throw new SQLException("Random commit exception!!!");
+		}
+	    else if (r.nextInt(200) == 0)
+		{
+		    conn.rollback();
+		    conn.close();
+		    throw new SQLException("Random Fatal Exception Occurred!!!");
+		}
+	    else
+		return method.invoke( conn, args );
+	}
+    }
+
+}
diff --git a/src/classes/com/mchange/v2/cfg/BasicMultiPropertiesConfig.java b/src/classes/com/mchange/v2/cfg/BasicMultiPropertiesConfig.java
new file mode 100644
index 0000000..a5bdd4d
--- /dev/null
+++ b/src/classes/com/mchange/v2/cfg/BasicMultiPropertiesConfig.java
@@ -0,0 +1,286 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cfg;
+
+import java.util.*;
+import java.io.*;
+import com.mchange.v2.log.*;
+
+public class BasicMultiPropertiesConfig extends MultiPropertiesConfig
+{
+    String[] rps;
+    Map  propsByResourcePaths = new HashMap();
+    Map  propsByPrefixes;
+
+    Properties propsByKey;
+
+    public BasicMultiPropertiesConfig(String[] resourcePaths)
+    { this( resourcePaths, null ); }
+
+    public BasicMultiPropertiesConfig(String[] resourcePaths, MLogger logger)
+    {
+	List goodPaths = new ArrayList();
+	for( int i = 0, len = resourcePaths.length; i < len; ++i )
+	    {
+		String rp = resourcePaths[i];
+		if ("/".equals(rp))
+		    {
+			try
+			    {
+				propsByResourcePaths.put( rp, System.getProperties() );
+				goodPaths.add( rp );
+			    }
+			catch (SecurityException e)
+			    {
+				if (logger != null)
+				    {
+					if ( logger.isLoggable( MLevel.WARNING ) )
+					    logger.log( MLevel.WARNING, 
+							"Read of system Properties blocked -- " +
+							"ignoring any configuration via System properties, and using Empty Properties! " +
+							"(But any configuration via a resource properties files is still okay!)",
+							e );
+				    }
+				else
+				    {
+					System.err.println("Read of system Properties blocked -- " +
+							   "ignoring any configuration via System properties, and using Empty Properties! " +
+							   "(But any configuration via a resource properties files is still okay!)");
+					e.printStackTrace(); 
+				    }
+			    }
+		    }
+		else
+		    {
+			Properties p = new Properties();
+			InputStream pis = MultiPropertiesConfig.class.getResourceAsStream( rp );
+			if ( pis != null )
+			    {
+				try
+				    {
+					p.load( pis );
+					propsByResourcePaths.put( rp, p );
+					goodPaths.add( rp );
+				    }
+				catch (IOException e)
+				    {
+					if (logger != null)
+					    {
+						if ( logger.isLoggable( MLevel.WARNING ) )
+						    logger.log( MLevel.WARNING, 
+								"An IOException occurred while loading configuration properties from resource path '" + rp + "'.",
+								e );
+					    }
+					else
+					    e.printStackTrace(); 
+				    }
+				finally
+				    {
+					try { if ( pis != null ) pis.close(); }
+					catch (IOException e) 
+					    { 
+						if (logger != null)
+						    {
+							if ( logger.isLoggable( MLevel.WARNING ) )
+							    logger.log( MLevel.WARNING, 
+									"An IOException occurred while closing InputStream from resource path '" + rp + "'.",
+									e );
+						    }
+						else
+						    e.printStackTrace(); 
+					    }
+				    }
+			    }
+			else
+			    {
+				if (logger != null)
+				    {
+					if ( logger.isLoggable( MLevel.FINE ) )
+					    logger.fine( "Configuration properties not found at ResourcePath '" + rp + "'. [logger name: " + logger.getName() + ']' );
+				    }
+// 			else if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+// 			    System.err.println("Configuration properties not found at ResourcePath '" + rp + "'." );
+			    }
+		    }
+	    }
+	
+	this.rps = (String[]) goodPaths.toArray( new String[ goodPaths.size() ] );
+	this.propsByPrefixes = Collections.unmodifiableMap( extractPrefixMapFromRsrcPathMap(rps, propsByResourcePaths) );
+	this.propsByResourcePaths = Collections.unmodifiableMap( propsByResourcePaths );
+	this.propsByKey = extractPropsByKey(rps, propsByResourcePaths);
+    }
+
+
+    private static String extractPrefix( String s )
+    {
+	int lastdot = s.lastIndexOf('.');
+	if ( lastdot < 0 )
+	    return null;
+	else
+	    return s.substring(0, lastdot);
+    }
+
+    private static Properties findProps(String rp, Map pbrp)
+    {
+	//System.err.println("findProps( " + rp + ", ... )");
+	Properties p;
+	
+	// MOVED THIS LOGIC INTO CONSTRUCTOR ABOVE, TO TREAT SYSTEM PROPS UNIFORMLY
+	// WITH THE REST, AND TO AVOID UNINTENTIONAL ATTEMPTS TO READ RESOURCE "/"
+	// AS STREAM -- swaldman, 2006-01-19
+	
+// 	if ( "/".equals( rp ) )
+// 	    {
+// 		try { p = System.getProperties(); }
+// 		catch ( SecurityException e )
+// 		    {
+// 			System.err.println(BasicMultiPropertiesConfig.class.getName() +
+// 					   " Read of system Properties blocked -- ignoring any configuration via System properties, and using Empty Properties! " +
+// 					   "(But any configuration via a resource properties files is still okay!)"); 
+// 			p = new Properties(); 
+// 		    }
+// 	    }
+// 	else
+	p = (Properties) pbrp.get( rp );
+	
+// 	System.err.println( p );
+
+	return p;
+    }
+
+    private static Properties extractPropsByKey( String[] resourcePaths, Map pbrp )
+    {
+	Properties out = new Properties();
+	for (int i = 0, len = resourcePaths.length; i < len; ++i)
+	    {
+		String rp = resourcePaths[i];
+		Properties p = findProps( rp, pbrp );
+		if (p == null)
+		    {
+			System.err.println("Could not find loaded properties for resource path: " + rp);
+			continue;
+		    }
+		for (Iterator ii = p.keySet().iterator(); ii.hasNext(); )
+		    {
+			Object kObj = ii.next();
+			if (!(kObj instanceof String))
+			    {
+				// note that we can not use the MLog library here, because initialization
+				// of that library depends on this function.
+				System.err.println( BasicMultiPropertiesConfig.class.getName() + ": " +
+						    "Properties object found at resource path " +
+						    ("/".equals(rp) ? "[system properties]" : "'" + rp + "'") +
+						    "' contains a key that is not a String: " +
+						    kObj);
+				System.err.println("Skipping...");
+				continue;
+			    }
+			Object vObj = p.get( kObj );
+			if (vObj != null && !(vObj instanceof String))
+			    {
+				// note that we can not use the MLog library here, because initialization
+				// of that library depends on this function.
+				System.err.println( BasicMultiPropertiesConfig.class.getName() + ": " +
+						    "Properties object found at resource path " +
+						    ("/".equals(rp) ? "[system properties]" : "'" + rp + "'") +
+						    " contains a value that is not a String: " +
+						    vObj);
+				System.err.println("Skipping...");
+				continue;
+			    }
+
+			String key = (String) kObj;
+			String val = (String) vObj;
+			out.put( key, val );
+		    }
+	    }
+	return out;
+    }
+
+    private static Map extractPrefixMapFromRsrcPathMap(String[] resourcePaths, Map pbrp)
+    {
+	Map out = new HashMap();
+	//for( Iterator ii = pbrp.values().iterator(); ii.hasNext(); )
+	for (int i = 0, len = resourcePaths.length; i < len; ++i)
+	    {
+		String rp = resourcePaths[i];
+		Properties p = findProps( rp, pbrp );
+		if (p == null)
+		    {
+			System.err.println(BasicMultiPropertiesConfig.class.getName() + " -- Could not find loaded properties for resource path: " + rp);
+			continue;
+		    }
+		for (Iterator jj = p.keySet().iterator(); jj.hasNext(); )
+		    {
+			Object kObj = jj.next();
+			if (! (kObj instanceof String))
+			    {
+				// note that we can not use the MLog library here, because initialization
+				// of that library depends on this function.
+				System.err.println( BasicMultiPropertiesConfig.class.getName() + ": " +
+						    "Properties object found at resource path " +
+						    ("/".equals(rp) ? "[system properties]" : "'" + rp + "'") +
+						    "' contains a key that is not a String: " +
+						    kObj);
+				System.err.println("Skipping...");
+				continue;
+			    }
+
+			String key = (String) kObj;
+			String prefix = extractPrefix( key );
+			while (prefix != null)
+			    {
+				Properties byPfx = (Properties) out.get( prefix );
+				if (byPfx == null)
+				    {
+					byPfx = new Properties();
+					out.put( prefix, byPfx );
+				    }
+				byPfx.put( key, p.get( key ) );
+
+				prefix=extractPrefix( prefix );
+			    }
+		    }
+	    }
+	return out;
+    }
+
+    public String[] getPropertiesResourcePaths()
+    { return (String[]) rps.clone(); }
+
+    public Properties getPropertiesByResourcePath(String path)
+    { 
+	Properties out = ((Properties) propsByResourcePaths.get( path )); 
+	return (out == null ? new Properties() : out);
+    }
+
+    public Properties getPropertiesByPrefix(String pfx)
+    {
+	Properties out = ((Properties) propsByPrefixes.get( pfx ));
+	return (out == null ? new Properties() : out);
+    }
+
+    public String getProperty( String key )
+    { return propsByKey.getProperty( key ); }
+}
diff --git a/src/classes/com/mchange/v2/cfg/CombinedMultiPropertiesConfig.java b/src/classes/com/mchange/v2/cfg/CombinedMultiPropertiesConfig.java
new file mode 100644
index 0000000..a7c8203
--- /dev/null
+++ b/src/classes/com/mchange/v2/cfg/CombinedMultiPropertiesConfig.java
@@ -0,0 +1,102 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cfg;
+
+import java.util.*;
+
+class CombinedMultiPropertiesConfig extends MultiPropertiesConfig
+{
+    MultiPropertiesConfig[] configs;
+    String[] resourcePaths;
+
+    CombinedMultiPropertiesConfig( MultiPropertiesConfig[] configs )
+    { 
+	this.configs = configs; 
+
+	List allPaths = new LinkedList();
+	for (int i = configs.length - 1; i >= 0; --i)
+	    {
+		String[] rps = configs[i].getPropertiesResourcePaths();
+		for (int j = rps.length - 1; j >= 0; --j)
+		    {
+			String rp = rps[j];
+			if (! allPaths.contains( rp ) )
+			    allPaths.add(0, rp);
+		    }
+	    }
+	this.resourcePaths = (String[]) allPaths.toArray( new String[ allPaths.size() ] );
+    }
+
+    public String[] getPropertiesResourcePaths()
+    { return (String[]) resourcePaths.clone(); }
+    
+    public Properties getPropertiesByResourcePath(String path)
+    {
+	for (int i = configs.length - 1; i >= 0; --i)
+	    {
+		MultiPropertiesConfig config = configs[i];
+		Properties check = config.getPropertiesByResourcePath(path);
+		if (check != null) 
+		    return check;
+	    }
+	return null;
+    }
+    
+    public Properties getPropertiesByPrefix(String pfx)
+    {
+	List entries = new LinkedList();
+	for (int i = configs.length - 1; i >= 0; --i)
+	    {
+		MultiPropertiesConfig config = configs[i];
+		Properties check = config.getPropertiesByPrefix(pfx);
+		if (check != null)
+		    entries.addAll( 0, check.entrySet() );
+	    }
+	if (entries.size() == 0)
+	    return null;
+	else
+	    {
+		Properties out = new Properties();
+		for (Iterator ii = entries.iterator(); ii.hasNext(); )
+		    {
+			Map.Entry entry = (Map.Entry) ii.next();
+			out.put( entry.getKey(), entry.getValue() );
+		    }
+		return out;
+	    }
+    }
+    
+    public String getProperty( String key )
+    {
+	for (int i = configs.length - 1; i >= 0; --i)
+	    {
+		MultiPropertiesConfig config = configs[i];
+		String check = config.getProperty(key);
+		if (check != null) 
+		    return check;
+	    }
+	return null;
+    }
+}
+
diff --git a/src/classes/com/mchange/v2/cfg/MultiPropertiesConfig.java b/src/classes/com/mchange/v2/cfg/MultiPropertiesConfig.java
new file mode 100644
index 0000000..8f17ee8
--- /dev/null
+++ b/src/classes/com/mchange/v2/cfg/MultiPropertiesConfig.java
@@ -0,0 +1,132 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cfg;
+
+import java.util.*;
+import java.io.*;
+import com.mchange.v2.log.*;
+
+/**
+ * MultiPropertiesConfig allows applications to accept configuration data
+ * from a more than one property file (each of which is to be loaded from
+ * a unique path using this class' ClassLoader's resource-loading mechanism),
+ * and permits access to property data via the resource path from which the
+ * properties were loaded, via the prefix of the property (where hierarchical 
+ * property names are presumed to be '.'-separated), and simply by key.
+ * In the by-key and by-prefix indices, when two definitions conflict, the
+ * key value pairing specified in the MOST RECENT properties file shadows
+ * earlier definitions, and files are loaded in the order of the list of
+ * resource paths provided a constructor.
+ *
+ * The rescource path "/" is a special case that always refers to System
+ * properties. No actual resource will be loaded.
+
+ * The class manages a special instance called "vmConfig" which is accessable
+ * via a static method. It's resource path is list specified by a text-file,
+ * itself a ClassLoader managed resource, which must be located at
+ * <tt>/com/mchange/v2/cfg/vmConfigResourcePaths.txt</tt>. This file should
+ * be one resource path per line, with blank lines ignored and lines beginning
+ * with '#' treated as comments.
+ */
+public abstract class MultiPropertiesConfig
+{
+    final static MultiPropertiesConfig EMPTY = new BasicMultiPropertiesConfig( new String[0] );
+
+    final static String VM_CONFIG_RSRC_PATHS = "/com/mchange/v2/cfg/vmConfigResourcePaths.txt";
+
+    static MultiPropertiesConfig vmConfig = null;
+
+    public static MultiPropertiesConfig read(String[] resourcePath, MLogger logger)
+    { return new BasicMultiPropertiesConfig( resourcePath, logger ); }
+
+    public static MultiPropertiesConfig read(String[] resourcePath)
+    { return new BasicMultiPropertiesConfig( resourcePath ); }
+
+    public static MultiPropertiesConfig combine( MultiPropertiesConfig[] configs )
+    { return new CombinedMultiPropertiesConfig( configs ); }
+
+    public static MultiPropertiesConfig readVmConfig(String[] defaultResources, String[] preemptingResources)
+    {
+	List l = new LinkedList();
+	if (defaultResources != null)
+	    l.add( read( defaultResources ) );
+	l.add( readVmConfig() );
+	if (preemptingResources != null)
+	    l.add( read( preemptingResources ) );
+	return combine( (MultiPropertiesConfig[]) l.toArray( new MultiPropertiesConfig[ l.size() ] ) );
+    }
+
+    public static MultiPropertiesConfig readVmConfig()
+    {
+	if ( vmConfig == null )
+	    {
+		List rps = new ArrayList();
+
+		BufferedReader br = null;
+		try
+		    {
+			InputStream is = MultiPropertiesConfig.class.getResourceAsStream( VM_CONFIG_RSRC_PATHS );
+			if ( is != null )
+			    {
+				br = new BufferedReader( new InputStreamReader( is, "8859_1" ) );
+				String rp;
+				while ((rp = br.readLine()) != null)
+				    {
+					rp = rp.trim();
+					if ("".equals( rp ) || rp.startsWith("#"))
+					    continue;
+					
+					rps.add( rp );
+				    }
+				vmConfig = new BasicMultiPropertiesConfig( (String[]) rps.toArray( new String[ rps.size() ] ) ); 
+			    }
+			else
+			    {
+				System.err.println("com.mchange.v2.cfg.MultiPropertiesConfig: Resource path list could not be found at resource path: " + VM_CONFIG_RSRC_PATHS);
+				System.err.println("com.mchange.v2.cfg.MultiPropertiesConfig: Using empty vmconfig.");
+				vmConfig = EMPTY;
+			    }
+		    }
+		catch (IOException e)
+		    { e.printStackTrace(); }
+		finally
+		    {
+			try { if ( br != null ) br.close(); }
+			catch (IOException e) { e.printStackTrace(); }
+		    }
+	    }
+	return vmConfig;
+    }
+
+    public boolean foundVmConfig()
+    { return vmConfig != EMPTY; }
+
+    public abstract String[] getPropertiesResourcePaths();
+
+    public abstract Properties getPropertiesByResourcePath(String path);
+
+    public abstract Properties getPropertiesByPrefix(String pfx);
+
+    public abstract String getProperty( String key );
+}
diff --git a/src/classes/com/mchange/v2/cfg/junit/BasicMultiPropertiesConfigJUnitTestCase.java b/src/classes/com/mchange/v2/cfg/junit/BasicMultiPropertiesConfigJUnitTestCase.java
new file mode 100644
index 0000000..fe63d8b
--- /dev/null
+++ b/src/classes/com/mchange/v2/cfg/junit/BasicMultiPropertiesConfigJUnitTestCase.java
@@ -0,0 +1,56 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.cfg.junit;
+
+import junit.framework.*;
+import com.mchange.v2.cfg.*;
+
+public final class BasicMultiPropertiesConfigJUnitTestCase extends TestCase
+{
+    final static String RP_A = "/com/mchange/v2/cfg/junit/a.properties";
+    final static String RP_B = "/com/mchange/v2/cfg/junit/b.properties";
+
+    public void testNoSystemConfig()
+    {
+	MultiPropertiesConfig mpc = new BasicMultiPropertiesConfig(new String[] {RP_A, RP_B});
+	//System.err.println(mpc.getProperty( "user.home" ));
+	assertTrue( "/b/home".equals( mpc.getProperty( "user.home" ) ) );
+    }
+
+    public void testSystemShadows()
+    {
+	MultiPropertiesConfig mpc = new BasicMultiPropertiesConfig(new String[] {RP_A, RP_B, "/"});
+	//System.err.println(mpc.getProperty( "user.home" ));
+	assertTrue( (! "/b/home".equals( mpc.getProperty( "user.home" ) ) ) && 
+		    (! "/a/home".equals( mpc.getProperty( "user.home" ) ) ) );
+    }
+
+    public void testSystemShadowed()
+    {
+	MultiPropertiesConfig mpc = new BasicMultiPropertiesConfig(new String[] {RP_A, "/", RP_B});
+	//System.err.println(mpc.getProperty( "user.home" ));
+	assertTrue( "/b/home".equals( mpc.getProperty( "user.home" ) ) );
+    }
+
+}
diff --git a/src/classes/com/mchange/v2/coalesce/AbstractStrongCoalescer.java b/src/classes/com/mchange/v2/coalesce/AbstractStrongCoalescer.java
new file mode 100644
index 0000000..0a7687e
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/AbstractStrongCoalescer.java
@@ -0,0 +1,54 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import java.util.*;
+
+class AbstractStrongCoalescer implements Coalescer
+{
+    Map coalesced;
+
+    AbstractStrongCoalescer( Map coalesced )
+    { this.coalesced = coalesced; }
+
+    public Object coalesce( Object o )
+    {
+	Object out = coalesced.get( o );
+	if ( out == null )
+	    {
+		coalesced.put( o , o );
+		out = o;
+	    }
+	return out;
+    }
+
+    public int countCoalesced()
+    { return coalesced.size(); }
+
+    public Iterator iterator()
+    { return new CoalescerIterator( coalesced.keySet().iterator() ); }
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/coalesce/AbstractWeakCoalescer.java b/src/classes/com/mchange/v2/coalesce/AbstractWeakCoalescer.java
new file mode 100644
index 0000000..fee4aaf
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/AbstractWeakCoalescer.java
@@ -0,0 +1,61 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import java.util.*;
+import java.lang.ref.WeakReference;
+
+class AbstractWeakCoalescer implements Coalescer
+{
+    Map wcoalesced;
+
+    AbstractWeakCoalescer( Map wcoalesced )
+    { this.wcoalesced = wcoalesced; }
+
+    public Object coalesce( Object o )
+    {
+	//System.err.println("AbstractWeakCoalescer.coalesce( " + o + " )");
+	Object out = null;
+
+	WeakReference wr = (WeakReference) wcoalesced.get( o );
+	if ( wr != null ) 
+	    out = wr.get(); //there is a conceivable race that would
+    	                    //permit wr be cleared
+	if ( out == null )
+	    {
+		wcoalesced.put( o , new WeakReference(o) );
+		out = o;
+	    }
+	return out;
+    }
+
+    public int countCoalesced()
+    { return wcoalesced.size(); }
+
+    public Iterator iterator()
+    { return new CoalescerIterator( wcoalesced.keySet().iterator() ); }
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/coalesce/CoalesceChecker.java b/src/classes/com/mchange/v2/coalesce/CoalesceChecker.java
new file mode 100644
index 0000000..736aa1c
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/CoalesceChecker.java
@@ -0,0 +1,40 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+public interface CoalesceChecker
+{
+    /**
+     *  @return true iff a and b should be considered equivalent,
+     *          so that a Coalescer should simply return whichever
+     *          Object it considers canonical.
+     */
+    public boolean checkCoalesce( Object a, Object b );
+
+    /**
+     *  Any two objects for which checkCoalese() would return true <b>must</b>
+     *  coalesce hash to the same value!!!
+     */
+    public int coalesceHash( Object a );
+}
diff --git a/src/classes/com/mchange/v2/coalesce/CoalesceIdenticator.java b/src/classes/com/mchange/v2/coalesce/CoalesceIdenticator.java
new file mode 100644
index 0000000..4d9786e
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/CoalesceIdenticator.java
@@ -0,0 +1,40 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import com.mchange.v1.identicator.*;
+
+class CoalesceIdenticator implements Identicator
+{
+    CoalesceChecker cc;
+
+    CoalesceIdenticator( CoalesceChecker cc )
+    { this.cc = cc; }
+
+    public boolean identical(Object a, Object b)
+    { return cc.checkCoalesce( a , b ); }
+
+    public int hash(Object o)
+    { return cc.coalesceHash( o ); }
+}
diff --git a/src/classes/com/mchange/v2/coalesce/Coalescer.java b/src/classes/com/mchange/v2/coalesce/Coalescer.java
new file mode 100644
index 0000000..492db70
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/Coalescer.java
@@ -0,0 +1,33 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import java.util.Iterator;
+
+public interface Coalescer
+{
+    public Object coalesce( Object o );
+    public int countCoalesced();
+    public Iterator iterator();
+}
diff --git a/src/classes/com/mchange/v2/coalesce/CoalescerFactory.java b/src/classes/com/mchange/v2/coalesce/CoalescerFactory.java
new file mode 100644
index 0000000..135e217
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/CoalescerFactory.java
@@ -0,0 +1,98 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+public final class CoalescerFactory
+{
+    /**
+     *  <p>Creates a "Coalescer" that coalesces Objects according to their
+     *  equals() method. Given a set of n Objects among whom equals() would
+     *  return true, calling coalescer.coalesce() in any order on any sequence 
+     *  of these Objects will always return a single "canonical" instance.</p>
+     *
+     *  <p>This method creates a weak, synchronized coalesecer, safe for use
+     *  by multiple Threads.</p>
+     */
+    public static Coalescer createCoalescer()
+    { return createCoalescer( true, true ); }
+
+    /**
+     *  <p>Creates a "Coalescer" that coalesces Objects according to their
+     *  equals() method. Given a set of n Objects among whom equals() would
+     *  return true, calling coalescer.coalesce() in any order on any sequence 
+     *  of these Objects will always return a single "canonical" instance.</p>
+     *
+     *  @param weak if true, the Coalescer will use WeakReferences to hold
+     *              its canonical instances, allowing them to be garbage
+     *              collected if they are nowhere in use.
+     *
+     *  @param synced if true, access to the Coalescer will be automatically
+     *                synchronized. if set to false, then users must manually
+     *                synchronize access.
+     */
+    public static Coalescer createCoalescer( boolean weak, boolean synced )
+    { return createCoalescer( null, weak, synced ); }
+
+    /**
+     *  <p>Creates a "Coalescer" that coalesces Objects according to the
+     *  checkCoalesce() method of a "CoalesceChecker". Given a set of 
+     *  n Objects among whom calling cc.checkCoalesce() on any pair would
+     *  return true, calling coalescer.coalesce() in any order on any sequence 
+     *  of these Objects will always return a single "canonical" instance.
+     *  This allows one to define immutable value Objects whose equals() 
+     *  method is a mere identity test -- one can use a Coalescer in a 
+     *  factory method to ensure that no two instances with the same values
+     *  are made available to clients.</p>
+     *
+     * @param cc CoalesceChecker that will be used to determine whether two
+     *           objects are equivalent and can be coalesced. [If cc is null, then two
+     *           objects will be coalesced iff o1.equals( o2 ).]
+     *
+     *  @param weak if true, the Coalescer will use WeakReferences to hold
+     *              its canonical instances, allowing them to be garbage
+     *              collected if they are nowhere in use.
+     *
+     *  @param synced if true, access to the Coalescer will be automatically
+     *                synchronized. if set to false, then users must manually
+     *                synchronize access.
+     */
+    public static Coalescer createCoalescer( CoalesceChecker cc, boolean weak, boolean synced )
+    {
+	Coalescer out;
+	if ( cc == null )
+	    {
+		out = ( weak ? 
+			(Coalescer) new WeakEqualsCoalescer() : 
+			(Coalescer) new StrongEqualsCoalescer() );
+	    }
+	else
+	    {
+		out = ( weak ? 
+			(Coalescer) new WeakCcCoalescer( cc ) : 
+			(Coalescer) new StrongCcCoalescer( cc ) );
+	    }
+	return ( synced ? new SyncedCoalescer( out ) : out );
+    }
+
+}
diff --git a/src/classes/com/mchange/v2/coalesce/CoalescerIterator.java b/src/classes/com/mchange/v2/coalesce/CoalescerIterator.java
new file mode 100644
index 0000000..55f8919
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/CoalescerIterator.java
@@ -0,0 +1,43 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import java.util.*;
+
+class CoalescerIterator implements Iterator
+{
+    Iterator inner;
+
+    CoalescerIterator(Iterator inner)
+    { this.inner = inner; }
+
+    public boolean hasNext()
+    { return inner.hasNext(); }
+    
+    public Object next()
+    { return inner.next(); }
+    
+    public void remove()
+    { throw new UnsupportedOperationException("Objects cannot be removed from a coalescer!"); }
+}
diff --git a/src/classes/com/mchange/v2/coalesce/StrongCcCoalescer.java b/src/classes/com/mchange/v2/coalesce/StrongCcCoalescer.java
new file mode 100644
index 0000000..13d1313
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/StrongCcCoalescer.java
@@ -0,0 +1,37 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import java.util.*;
+import com.mchange.v1.identicator.IdHashMap;
+
+final class StrongCcCoalescer extends AbstractStrongCoalescer
+    implements Coalescer
+{
+    StrongCcCoalescer( CoalesceChecker cc )
+    { super( new IdHashMap( new CoalesceIdenticator( cc ) ) ); }
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/coalesce/StrongEqualsCoalescer.java b/src/classes/com/mchange/v2/coalesce/StrongEqualsCoalescer.java
new file mode 100644
index 0000000..0821a37
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/StrongEqualsCoalescer.java
@@ -0,0 +1,37 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import java.util.*;
+import java.lang.ref.WeakReference;
+
+final class StrongEqualsCoalescer extends AbstractStrongCoalescer 
+    implements Coalescer
+{
+    StrongEqualsCoalescer()
+    { super( new HashMap() ); }
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/coalesce/SyncedCoalescer.java b/src/classes/com/mchange/v2/coalesce/SyncedCoalescer.java
new file mode 100644
index 0000000..b5bf994
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/SyncedCoalescer.java
@@ -0,0 +1,43 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import java.util.Iterator;
+
+class SyncedCoalescer implements Coalescer
+{
+    Coalescer inner;
+    
+    public SyncedCoalescer( Coalescer inner )
+    { this.inner = inner; }
+    
+    public synchronized Object coalesce( Object o )
+    { return inner.coalesce( o ); }
+    
+    public synchronized int countCoalesced()
+    { return inner.countCoalesced(); }
+
+    public synchronized Iterator iterator()
+    { return inner.iterator(); }
+}
diff --git a/src/classes/com/mchange/v2/coalesce/WeakCcCoalescer.java b/src/classes/com/mchange/v2/coalesce/WeakCcCoalescer.java
new file mode 100644
index 0000000..e380928
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/WeakCcCoalescer.java
@@ -0,0 +1,37 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import java.util.*;
+import java.lang.ref.WeakReference;
+import com.mchange.v1.identicator.IdWeakHashMap;
+
+final class WeakCcCoalescer extends AbstractWeakCoalescer implements Coalescer
+{
+    WeakCcCoalescer(CoalesceChecker cc)
+    { super( new IdWeakHashMap( new CoalesceIdenticator( cc ) ) ); }
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/coalesce/WeakEqualsCoalescer.java b/src/classes/com/mchange/v2/coalesce/WeakEqualsCoalescer.java
new file mode 100644
index 0000000..898f19b
--- /dev/null
+++ b/src/classes/com/mchange/v2/coalesce/WeakEqualsCoalescer.java
@@ -0,0 +1,36 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.coalesce;
+
+import java.util.*;
+import java.lang.ref.WeakReference;
+
+class WeakEqualsCoalescer extends AbstractWeakCoalescer
+{
+    WeakEqualsCoalescer()
+    { super( new WeakHashMap() ); }
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/codegen/CodegenUtils.java b/src/classes/com/mchange/v2/codegen/CodegenUtils.java
new file mode 100644
index 0000000..feac274
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/CodegenUtils.java
@@ -0,0 +1,175 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen;
+
+import java.lang.reflect.*;
+import java.io.File;
+import java.io.Writer;
+import com.mchange.v1.lang.ClassUtils;
+
+public final class CodegenUtils
+{
+    public static String getModifierString( int modifiers )
+    {
+	StringBuffer sb = new StringBuffer(32);
+	if ( Modifier.isPublic( modifiers ) )
+	    sb.append("public ");
+	if ( Modifier.isProtected( modifiers ) )
+	    sb.append("protected ");
+	if ( Modifier.isPrivate( modifiers ) )
+	    sb.append("private ");
+	if ( Modifier.isAbstract( modifiers ) )
+	    sb.append("abstract ");
+	if ( Modifier.isStatic( modifiers ) )
+	    sb.append("static ");
+	if ( Modifier.isFinal( modifiers ) )
+	    sb.append("final ");
+	if ( Modifier.isSynchronized( modifiers ) )
+	    sb.append("synchronized ");
+	if ( Modifier.isTransient( modifiers ) )
+	    sb.append("transient ");
+	if ( Modifier.isVolatile( modifiers ) )
+	    sb.append("volatile ");
+	if ( Modifier.isStrict( modifiers ) )
+	    sb.append("strictfp ");
+	if ( Modifier.isNative( modifiers ) )
+	    sb.append("native ");
+	if ( Modifier.isInterface( modifiers ) ) //????
+	    sb.append("interface ");
+	return sb.toString().trim();
+    }
+
+    public static Class unarrayClass( Class cl )
+    {
+	Class out = cl;
+	while ( out.isArray() )
+	    out = out.getComponentType();
+	return out;
+    }
+
+    public static boolean inSamePackage(String cn1, String cn2)
+    {
+       int pkgdot = cn1.lastIndexOf('.');
+       int pkgdot2 = cn2.lastIndexOf('.');
+
+       //always return true of one class is a primitive or unpackages
+       if (pkgdot < 0 || pkgdot2 < 0)
+           return true;
+       if ( cn1.substring(0, pkgdot).equals(cn1.substring(0, pkgdot)) )
+       {
+          if (cn2.indexOf('.') >= 0)
+            return false;
+          else
+            return true;
+       }
+       else
+         return false;
+    }
+
+    /**
+     * @return fully qualified class name last element
+     */
+    public static String fqcnLastElement(String fqcn)
+    { return ClassUtils.fqcnLastElement( fqcn ); }
+
+    public static String methodSignature( Method m )
+    { return methodSignature( m, null ); }
+
+    public static String methodSignature( Method m, String[] argNames )
+    { return methodSignature( Modifier.PUBLIC, m, argNames ); }
+
+    public static String methodSignature( int modifiers, Method m, String[] argNames )
+    {
+	StringBuffer sb = new StringBuffer(256);
+        sb.append(getModifierString(modifiers));
+	sb.append(' ');
+	sb.append( ClassUtils.simpleClassName( m.getReturnType() ) );
+	sb.append(' ');
+	sb.append( m.getName() );
+	sb.append('(');
+        Class[] cls = m.getParameterTypes();
+        for(int i = 0, len = cls.length; i < len; ++i)
+        {
+           if (i != 0)
+             sb.append(", ");
+           sb.append( ClassUtils.simpleClassName( cls[i] ) );
+	   sb.append(' ');
+           sb.append( argNames == null ? String.valueOf((char) ('a' + i)) : argNames[i] );
+        }
+        sb.append(')');
+	Class[] excClasses = m.getExceptionTypes();
+	if (excClasses.length > 0)
+        {
+           sb.append(" throws ");
+           for (int i = 0, len = excClasses.length; i < len; ++i)
+           {
+             if (i != 0)
+               sb.append(", ");
+             sb.append( ClassUtils.simpleClassName( excClasses[i] ) );
+           }   
+        }
+        return sb.toString();
+    }
+
+    public static String methodCall( Method m )
+    { return methodCall( m, null ); }
+
+    public static String methodCall( Method m, String[] argNames )
+    {
+       StringBuffer sb = new StringBuffer(256);
+       sb.append( m.getName() );
+       sb.append('(');
+        Class[] cls = m.getParameterTypes();
+        for(int i = 0, len = cls.length; i < len; ++i)
+        {
+           if (i != 0)
+             sb.append(", ");
+           sb.append( argNames == null ? generatedArgumentName( i ) : argNames[i] );
+        }
+        sb.append(')');
+	return sb.toString();
+    } 
+
+    public static String generatedArgumentName( int index )
+    { return String.valueOf((char) ('a' + index)); }
+
+    public static String simpleClassName( Class cl )
+    { return ClassUtils.simpleClassName( cl ); }
+
+    public static IndentedWriter toIndentedWriter( Writer w )
+    { return (w instanceof IndentedWriter ? (IndentedWriter) w : new IndentedWriter(w)); }
+
+    public static String packageNameToFileSystemDirPath(String packageName)
+    {
+	StringBuffer sb = new StringBuffer( packageName );
+	for (int i = 0, len = sb.length(); i < len; ++i)
+	    if ( sb.charAt(i) == '.' )
+		sb.setCharAt(i, File.separatorChar);
+	sb.append( File.separatorChar );
+	return sb.toString();
+    }
+
+    private CodegenUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/codegen/IndentedWriter.java b/src/classes/com/mchange/v2/codegen/IndentedWriter.java
new file mode 100644
index 0000000..a84e0dd
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/IndentedWriter.java
@@ -0,0 +1,35 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen;
+
+import java.io.*;
+
+/**
+ * @deprecated -- please user com.mchange.v2.io.IndentedWriter
+ */
+public class IndentedWriter extends com.mchange.v2.io.IndentedWriter
+{
+    public IndentedWriter( Writer out )
+    { super( out ); }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/BeanExtractingGeneratorExtension.java b/src/classes/com/mchange/v2/codegen/bean/BeanExtractingGeneratorExtension.java
new file mode 100644
index 0000000..0dce8ce
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/BeanExtractingGeneratorExtension.java
@@ -0,0 +1,121 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import java.lang.reflect.Modifier;
+import java.io.IOException;
+import com.mchange.v2.codegen.CodegenUtils;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public class BeanExtractingGeneratorExtension implements GeneratorExtension 
+{
+    int ctor_modifiers   = Modifier.PUBLIC;
+    int method_modifiers = Modifier.PRIVATE;
+
+    public void setConstructorModifiers( int ctor_modifiers )
+    { this.ctor_modifiers = ctor_modifiers; }
+
+    public int getConstructorModifiers()
+    { return ctor_modifiers; }
+
+    public void setExtractMethodModifiers( int method_modifiers )
+    { this.method_modifiers = method_modifiers; }
+
+    public int getExtractMethodModifiers()
+    { return method_modifiers; }
+
+    public Collection extraGeneralImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraSpecificImports()
+    {
+	Set set = new HashSet();
+	set.add("java.beans.BeanInfo");
+	set.add("java.beans.PropertyDescriptor");
+	set.add("java.beans.Introspector");
+	set.add("java.beans.IntrospectionException");
+	set.add("java.lang.reflect.InvocationTargetException");
+	return set;
+    }
+
+    public Collection extraInterfaceNames()
+    { return Collections.EMPTY_SET; }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	iw.println("private static Class[] NOARGS = new Class[0];");
+	iw.println();
+	iw.print( CodegenUtils.getModifierString( method_modifiers ) );
+	iw.print(" void extractPropertiesFromBean( Object bean ) throws InvocationTargetException, IllegalAccessException, IntrospectionException");
+	iw.println("{");
+	iw.upIndent();
+
+	iw.println("BeanInfo bi = Introspector.getBeanInfo( bean.getClass() );");
+	iw.println("PropertyDescriptor[] pds = bi.getPropertyDescriptors();");
+	iw.println("for (int i = 0, len = pds.length; i < len; ++i)");
+	iw.println("{");
+	iw.upIndent();
+
+	for (int i = 0, len = props.length; i < len; ++i)
+	    {
+		iw.println("if (\"" + props[i].getName() + "\".equals( pds[i].getName() ) )");
+		iw.upIndent();
+		iw.println("this." + props[i].getName() + " = " + extractorExpr( props[i], propTypes[i] ) + ';');
+		iw.downIndent();
+	    }
+	iw.println("}"); //for loop
+
+
+	iw.downIndent();
+	iw.println("}"); //method
+	iw.println();
+	iw.print( CodegenUtils.getModifierString( ctor_modifiers ) );
+	iw.println(' ' + info.getClassName() + "( Object bean ) throws InvocationTargetException, IllegalAccessException, IntrospectionException");
+	iw.println("{");
+	iw.upIndent();
+	iw.println("extractPropertiesFromBean( bean );");
+	iw.downIndent();
+	iw.println("}");
+    }
+
+    private String extractorExpr( Property prop, Class propType )
+    {
+	if ( propType.isPrimitive() )
+	    {
+		String castType = BeangenUtils.capitalize( prop.getSimpleTypeName() );
+		String valueMethod = prop.getSimpleTypeName() + "Value()";
+
+		if ( propType == char.class)
+		    castType = "Character";
+		else if ( propType == int.class)
+		    castType = "Integer";
+
+		return "((" + castType + ") pds[i].getReadMethod().invoke( bean, NOARGS ))." + valueMethod;
+	    }
+	else
+	    return "(" + prop.getSimpleTypeName() + ") pds[i].getReadMethod().invoke( bean, NOARGS )";
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/BeangenUtils.java b/src/classes/com/mchange/v2/codegen/bean/BeangenUtils.java
new file mode 100644
index 0000000..cf86f41
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/BeangenUtils.java
@@ -0,0 +1,247 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.Comparator;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import com.mchange.v1.lang.ClassUtils;
+import com.mchange.v2.codegen.CodegenUtils;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public final class BeangenUtils
+{
+    public final static Comparator PROPERTY_COMPARATOR = new Comparator()
+    {
+	public int compare(Object a, Object b)
+	{
+	    Property aa = (Property) a;
+	    Property bb = (Property) b;
+
+	    return String.CASE_INSENSITIVE_ORDER.compare(aa.getName(), bb.getName() );
+	}
+     };
+
+    public static String capitalize( String propName )
+    {
+	char c = propName.charAt( 0 );
+	return Character.toUpperCase(c) + propName.substring(1);
+    }
+
+//     public static Class[] attemptResolveTypes(ClassInfo info, Property[] props)
+//     {
+// 	String[] gen = info.getGeneralImports();
+// 	String[] spc = info.getSpecificImports();
+
+// 	Class[] out = new Class[ props.length ];
+// 	for ( int i = 0, len = props.length; i < len; ++i )
+// 	    {
+// 		String name = props[i].getSimpleTypeName();
+// 		try 
+// 		    { out[i] = ClassUtils.forName( name , gen, spc ); }
+// 		catch ( Exception e )
+// 		    {
+// 			e.printStackTrace();
+// 			System.err.println("WARNING: " + this.getClass().getName() + " could not resolve " +
+// 					   "property type '" + name + "'.");
+// 			out[i] = null;
+// 		    }
+// 	    }
+//     }
+
+    public static void writeExplicitDefaultConstructor( int ctor_modifiers, ClassInfo info, IndentedWriter iw) throws IOException
+    {
+	iw.print( CodegenUtils.getModifierString( ctor_modifiers ) );
+	iw.println(' ' + info.getClassName() + "()");
+	iw.println("{}");
+    }
+
+
+    public static void writeArgList(Property[] props, boolean declare_types, IndentedWriter iw ) throws IOException
+    {
+	for (int i = 0, len = props.length; i < len; ++i)
+	    {
+		if (i != 0)
+		    iw.print(", ");
+		if (declare_types)
+		    iw.print(props[i].getSimpleTypeName() + ' ');
+		iw.print( props[i].getName() );
+	    }
+    }
+
+    /**
+     * @deprecated use writePropertyVariable
+     */
+    public static void writePropertyMember( Property prop, IndentedWriter iw ) throws IOException
+    { writePropertyVariable( prop, iw ); }
+
+    public static void writePropertyVariable( Property prop, IndentedWriter iw ) throws IOException
+    { writePropertyVariable( prop, prop.getDefaultValueExpression(), iw ); }
+
+    /**
+     * @deprecated use writePropertyVariable
+     */
+    public static void writePropertyMember( Property prop, String defaultValueExpression, IndentedWriter iw ) throws IOException
+    { writePropertyVariable( prop, defaultValueExpression, iw ); }
+
+    public static void writePropertyVariable( Property prop, String defaultValueExpression, IndentedWriter iw ) throws IOException
+    {
+	iw.print( CodegenUtils.getModifierString( prop.getVariableModifiers() ) );
+	iw.print( ' ' + prop.getSimpleTypeName() + ' ' + prop.getName());
+	String dflt = defaultValueExpression;
+	if (dflt != null)
+	    iw.print( " = " + dflt );
+	iw.println(';');
+    }
+
+    public static void writePropertyGetter( Property prop, IndentedWriter iw ) throws IOException
+    { writePropertyGetter( prop, prop.getDefensiveCopyExpression(), iw ); }
+
+    public static void writePropertyGetter( Property prop, String defensiveCopyExpression, IndentedWriter iw ) throws IOException
+    {
+	String pfx = ("boolean".equals( prop.getSimpleTypeName() ) ? "is" : "get" );
+	iw.print( CodegenUtils.getModifierString( prop.getGetterModifiers() ) );
+	iw.println(' ' + prop.getSimpleTypeName() + ' ' + pfx + BeangenUtils.capitalize( prop.getName() ) + "()");
+	String retVal = defensiveCopyExpression; 
+	if (retVal == null) retVal = prop.getName();
+	iw.println("{ return " + retVal + "; }");
+    }
+
+    public static void writePropertySetter( Property prop, IndentedWriter iw ) 
+	throws IOException
+    { writePropertySetter( prop, prop.getDefensiveCopyExpression(), iw ); }
+
+    public static void writePropertySetter( Property prop, String setterDefensiveCopyExpression, IndentedWriter iw ) 
+	throws IOException
+    {
+	String setVal = setterDefensiveCopyExpression;
+	if (setVal == null) setVal = prop.getName();
+	String usualGetExpression = ("this." + prop.getName());
+	String usualSetStatement = ("this." + prop.getName() + " = " + setVal + ';');
+	writePropertySetterWithGetExpressionSetStatement(prop, usualGetExpression, usualSetStatement, iw);
+    }
+
+    public static void writePropertySetterWithGetExpressionSetStatement( Property prop, String getExpression, String setStatement, IndentedWriter iw ) 
+	throws IOException
+    {
+	iw.print( CodegenUtils.getModifierString( prop.getSetterModifiers() ) );
+	iw.print(" void set" + BeangenUtils.capitalize( prop.getName() ) + "( " + prop.getSimpleTypeName() + ' ' + prop.getName() + " )");
+	if ( prop.isConstrained() )
+	    iw.println(" throws PropertyVetoException");
+	else
+	    iw.println();
+	iw.println('{');
+	iw.upIndent();
+
+	if ( changeMarked( prop ) )
+	    {
+		iw.println( prop.getSimpleTypeName() + " oldVal = " + getExpression + ';');
+
+		String oldValExpr = "oldVal";
+		String newValExpr = prop.getName();
+		String changeCheck;
+
+		String simpleTypeName = prop.getSimpleTypeName();
+		if ( ClassUtils.isPrimitive( simpleTypeName ) )
+		    {
+			Class propType = ClassUtils.classForPrimitive( simpleTypeName );
+
+			// PropertyChangeSupport already has overloads
+			// for boolean and int 
+			if (propType == byte.class)
+			    {
+				oldValExpr  = "new Byte( "+ oldValExpr +" )";
+				newValExpr  = "new Byte( "+ newValExpr +" )";
+			    }
+			else if (propType == char.class)
+			    {
+				oldValExpr  = "new Character( "+ oldValExpr +" )";
+				newValExpr  = "new Character( "+ newValExpr +" )";
+			    }
+			else if (propType == short.class)
+			    {
+				oldValExpr  = "new Short( "+ oldValExpr +" )";
+				newValExpr  = "new Short( "+ newValExpr +" )";
+			    }
+			else if (propType == float.class)
+			    {
+				oldValExpr  = "new Float( "+ oldValExpr +" )";
+				newValExpr  = "new Float( "+ newValExpr +" )";
+			    }
+			else if (propType == double.class)
+			    {
+				oldValExpr  = "new Double( "+ oldValExpr +" )";
+				newValExpr  = "new Double( "+ newValExpr +" )";
+			    }
+
+			changeCheck = "oldVal != " + prop.getName();
+		    }
+		else
+		    changeCheck = "! eqOrBothNull( oldVal, " + prop.getName() + " )";
+			
+		if ( prop.isConstrained() )
+		    {
+			iw.println("if ( " + changeCheck + " )");
+			iw.upIndent();
+			iw.println("vcs.fireVetoableChange( \"" + prop.getName() + "\", " + oldValExpr + ", " + newValExpr + " );");
+			iw.downIndent();
+		    }
+
+		iw.println( setStatement );
+				
+		if ( prop.isBound() )
+		    {
+			iw.println("if ( " + changeCheck + " )");
+			iw.upIndent();
+			iw.println("pcs.firePropertyChange( \"" + prop.getName() + "\", " + oldValExpr + ", " + newValExpr + " );");
+			iw.downIndent();
+		    }
+	    }
+	else
+	    iw.println( setStatement );
+
+	iw.downIndent();
+	iw.println('}');
+    }
+
+    public static boolean hasBoundProperties(Property[] props)
+    {
+	for (int i = 0, len = props.length; i < len; ++i)
+	    if (props[i].isBound()) return true;
+	return false;
+    }
+
+    public static boolean hasConstrainedProperties(Property[] props)
+    {
+	for (int i = 0, len = props.length; i < len; ++i)
+	    if (props[i].isConstrained()) return true;
+	return false;
+    }
+
+    private static boolean changeMarked( Property prop )
+    { return prop.isBound() || prop.isConstrained(); }
+
+    private BeangenUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/ClassInfo.java b/src/classes/com/mchange/v2/codegen/bean/ClassInfo.java
new file mode 100644
index 0000000..08bd7db
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/ClassInfo.java
@@ -0,0 +1,35 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+public interface ClassInfo
+{
+    public String   getPackageName();
+    public int      getModifiers();
+    public String   getClassName();
+    public String   getSuperclassName();
+    public String[] getInterfaceNames();
+    public String[] getGeneralImports();
+    public String[] getSpecificImports();
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/CloneableExtension.java b/src/classes/com/mchange/v2/codegen/bean/CloneableExtension.java
new file mode 100644
index 0000000..e245bb9
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/CloneableExtension.java
@@ -0,0 +1,122 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import java.io.IOException;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public class CloneableExtension implements GeneratorExtension
+{
+    boolean export_public;
+    boolean exception_swallowing;
+
+    String mLoggerName = null;
+
+    public boolean isExportPublic()
+    { return export_public; }
+
+    public void setExportPublic(boolean export_public)
+    { this.export_public = export_public; }
+
+    public boolean isExceptionSwallowing()
+    { return exception_swallowing; }
+
+    public void setExceptionSwallowing(boolean exception_swallowing)
+    { this.exception_swallowing = exception_swallowing; }
+
+    public String getMLoggerName()
+    { return mLoggerName; }
+
+    public void setMLoggerName( String mLoggerName )
+    { this.mLoggerName = mLoggerName; }
+
+    public CloneableExtension(boolean export_public, boolean exception_swallowing)
+    { 
+	this.export_public = export_public; 
+	this.exception_swallowing = exception_swallowing;
+    }
+
+    public CloneableExtension()
+    { this ( true, false ); }
+
+    public Collection extraGeneralImports()
+    { return (mLoggerName == null ? ((Collection) Collections.EMPTY_SET) : ((Collection) Arrays.asList( new String[] {"com.mchange.v2.log"} )) ); }
+
+    public Collection extraSpecificImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraInterfaceNames()
+    {
+	Set set = new HashSet();
+	set.add( "Cloneable" );
+	return set;
+    }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	if (export_public)
+	    {
+		iw.print("public Object clone()");
+		if ( !exception_swallowing )
+		    iw.println(" throws CloneNotSupportedException");
+		else
+		    iw.println();
+		iw.println("{");
+		iw.upIndent();
+		if ( exception_swallowing )
+		    {
+			iw.println("try");
+			iw.println("{");
+			iw.upIndent();
+		    }
+		iw.println( "return super.clone();" );
+		if ( exception_swallowing )
+		    {
+			iw.downIndent();
+			iw.println("}");
+			iw.println("catch (CloneNotSupportedException e)");
+			iw.println("{");
+			iw.upIndent();
+			if (mLoggerName == null)
+			    iw.println("e.printStackTrace();");
+			else
+			    {
+				iw.println("if ( " + mLoggerName + ".isLoggable( MLevel.FINE ) )" );
+				iw.upIndent();
+				iw.println( mLoggerName + ".log( MLevel.FINE, \"Inconsistent clone() definitions between subclass and superclass! \", e );");
+				iw.downIndent();
+			    }
+			iw.println("throw new RuntimeException(\"Inconsistent clone() definitions between subclass and superclass! \" + e);" );
+			iw.downIndent();
+			iw.println("}");
+		    }
+			
+		iw.downIndent();
+		iw.println("}");
+	    }
+	//else, write nothing... just add Cloneable to interface definitions...
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/CompleteConstructorGeneratorExtension.java b/src/classes/com/mchange/v2/codegen/bean/CompleteConstructorGeneratorExtension.java
new file mode 100644
index 0000000..7cefb9f
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/CompleteConstructorGeneratorExtension.java
@@ -0,0 +1,67 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import java.lang.reflect.Modifier;
+import java.io.IOException;
+import com.mchange.v2.codegen.CodegenUtils;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public class CompleteConstructorGeneratorExtension implements GeneratorExtension 
+{
+    int ctor_modifiers = Modifier.PUBLIC;
+
+    public Collection extraGeneralImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraSpecificImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraInterfaceNames()
+    { return Collections.EMPTY_SET; }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	iw.print( CodegenUtils.getModifierString( ctor_modifiers ) );
+	iw.print( info.getClassName() + "( ");
+	BeangenUtils.writeArgList(props, true, iw);
+	iw.println(" )");
+	iw.println("{");
+	iw.upIndent();
+
+	for (int i = 0, len = props.length; i < len; ++i)
+	    {
+		iw.print("this." + props[i].getName() + " = ");
+		String setExp = props[i].getDefensiveCopyExpression();
+		if (setExp == null)
+		    setExp = props[i].getName();
+		iw.println(setExp + ';');
+	    }
+
+	iw.downIndent();
+	iw.println("}");
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/CopyConstructorGeneratorExtension.java b/src/classes/com/mchange/v2/codegen/bean/CopyConstructorGeneratorExtension.java
new file mode 100644
index 0000000..53685dd
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/CopyConstructorGeneratorExtension.java
@@ -0,0 +1,73 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import java.lang.reflect.Modifier;
+import java.io.IOException;
+import com.mchange.v2.codegen.CodegenUtils;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public class CopyConstructorGeneratorExtension implements GeneratorExtension 
+{
+    int ctor_modifiers = Modifier.PUBLIC;
+
+    public Collection extraGeneralImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraSpecificImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraInterfaceNames()
+    { return Collections.EMPTY_SET; }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	iw.print( CodegenUtils.getModifierString( ctor_modifiers ) );
+	iw.print(" " + info.getClassName() + "( ");
+	iw.print( info.getClassName() + " copyMe" );
+	iw.println(" )");
+	iw.println("{");
+	iw.upIndent();
+
+	for (int i = 0, len = props.length; i < len; ++i)
+	    {
+		String propGetterMethodCall;
+		if (propTypes[i] == boolean.class)
+		    propGetterMethodCall = "is" + BeangenUtils.capitalize( props[i].getName() ) + "()";
+		else
+		    propGetterMethodCall = "get" + BeangenUtils.capitalize( props[i].getName() ) + "()";
+		iw.println(props[i].getSimpleTypeName() + ' ' + props[i].getName() + " = copyMe." + propGetterMethodCall + ';');
+		iw.print("this." + props[i].getName() + " = ");
+		String setExp = props[i].getDefensiveCopyExpression();
+		if (setExp == null)
+		    setExp = props[i].getName();
+		iw.println(setExp + ';');
+	    }
+
+	iw.downIndent();
+	iw.println("}");
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/ExplicitDefaultConstructorGeneratorExtension.java b/src/classes/com/mchange/v2/codegen/bean/ExplicitDefaultConstructorGeneratorExtension.java
new file mode 100644
index 0000000..2e93e43
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/ExplicitDefaultConstructorGeneratorExtension.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import java.lang.reflect.Modifier;
+import java.io.IOException;
+import com.mchange.v2.codegen.CodegenUtils;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public class ExplicitDefaultConstructorGeneratorExtension implements GeneratorExtension 
+{
+    int ctor_modifiers = Modifier.PUBLIC;
+
+    public Collection extraGeneralImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraSpecificImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraInterfaceNames()
+    { return Collections.EMPTY_SET; }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    { BeangenUtils.writeExplicitDefaultConstructor( ctor_modifiers, info, iw); }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/ExplicitPropsConstructorGeneratorExtension.java b/src/classes/com/mchange/v2/codegen/bean/ExplicitPropsConstructorGeneratorExtension.java
new file mode 100644
index 0000000..17330e4
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/ExplicitPropsConstructorGeneratorExtension.java
@@ -0,0 +1,120 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import com.mchange.v2.log.*;
+
+import java.lang.reflect.Modifier;
+import java.io.IOException;
+import com.mchange.v2.codegen.CodegenUtils;
+import com.mchange.v2.codegen.IndentedWriter;
+
+
+/**
+ * Writes a constructor that takes an explicitly listed subset of a bean's properties
+ * for its arguments, and sets these properties initial values appropriately.
+ * Skips any specified names for properties that are not found in a bean being generated.
+ * Writes nothing if there are none of the property names are properties of the bean.
+ */
+public class ExplicitPropsConstructorGeneratorExtension implements GeneratorExtension 
+{
+    final static MLogger logger = MLog.getLogger( ExplicitPropsConstructorGeneratorExtension.class );
+
+    String[] propNames;
+
+    boolean skips_silently = false;
+
+    public ExplicitPropsConstructorGeneratorExtension()
+    {}
+
+    public ExplicitPropsConstructorGeneratorExtension(String[] propNames)
+    { this.propNames = propNames; }
+
+    public String[] getPropNames()
+    { return (String[]) propNames.clone(); }
+
+    public void setPropNames(String[] propNames)
+    { this.propNames = (String[]) propNames.clone(); }
+
+    public boolean isSkipsSilently()
+    { return skips_silently; }
+
+    public void setsSkipsSilently( boolean skips_silently )
+    { this.skips_silently = skips_silently; }
+
+    int ctor_modifiers = Modifier.PUBLIC;
+
+    public Collection extraGeneralImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraSpecificImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraInterfaceNames()
+    { return Collections.EMPTY_SET; }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	Map propNamesToProps = new HashMap();
+	for (int i = 0, len = props.length; i < len; ++i)
+	    propNamesToProps.put( props[i].getName(), props[i] );
+
+	List subPropsList = new ArrayList( propNames.length );
+	for (int i = 0, len = propNames.length; i < len; ++i)
+	    {
+		Property p = (Property) propNamesToProps.get( propNames[i] );
+		if ( p == null )
+		    logger.warning("Could not include property '" + propNames[i] +"' in explicit-props-constructor generated for bean class '" +
+				   info.getClassName() +"' because the property is not defined for the bean. Skipping.");
+		else
+		    subPropsList.add(p);
+	    }
+
+	if (subPropsList.size() > 0)
+	    {
+		Property[] subProps = (Property[]) subPropsList.toArray( new Property[ subPropsList.size() ] );
+
+		iw.print( CodegenUtils.getModifierString( ctor_modifiers ) );
+		iw.print( info.getClassName() + "( ");
+		BeangenUtils.writeArgList(subProps, true, iw);
+		iw.println(" )");
+		iw.println("{");
+		iw.upIndent();
+		
+		for (int i = 0, len = subProps.length; i < len; ++i)
+		    {
+			iw.print("this." + subProps[i].getName() + " = ");
+			String setExp = subProps[i].getDefensiveCopyExpression();
+			if (setExp == null)
+			    setExp = subProps[i].getName();
+			iw.println(setExp + ';');
+		    }
+		
+		iw.downIndent();
+		iw.println("}");
+	    }
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/GeneratorExtension.java b/src/classes/com/mchange/v2/codegen/bean/GeneratorExtension.java
new file mode 100644
index 0000000..7aa2e49
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/GeneratorExtension.java
@@ -0,0 +1,42 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import java.io.IOException;
+import com.mchange.v2.codegen.IndentedWriter;
+
+/**
+ * By the time generate(...) is called, all extra interfaces and imports from all
+ * GeneratorExtensions should be incorporated into the passed-in ClassInfo object.
+ */
+public interface GeneratorExtension
+{
+    public Collection extraGeneralImports();
+    public Collection extraSpecificImports();
+    public Collection extraInterfaceNames();
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException;
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/IndirectingSerializableExtension.java b/src/classes/com/mchange/v2/codegen/bean/IndirectingSerializableExtension.java
new file mode 100644
index 0000000..8e606ae
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/IndirectingSerializableExtension.java
@@ -0,0 +1,153 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import java.io.Serializable;
+import java.io.IOException;
+import com.mchange.v2.codegen.IndentedWriter;
+import com.mchange.v2.ser.IndirectPolicy;
+
+public class IndirectingSerializableExtension extends SerializableExtension
+{
+    protected String findIndirectorExpr;
+    protected String indirectorClassName;
+
+    /**
+     * We expect this indirector to be a public class with a public no_arg ctor;
+     * If you need the indirector initialized somehow, you'll have to extend
+     * the class.
+     *
+     * @see #writeInitializeIndirector
+     * @see #writeExtraDeclarations
+     */
+    public IndirectingSerializableExtension( String indirectorClassName )
+    { 
+	this.indirectorClassName = indirectorClassName;
+	this.findIndirectorExpr = "new " + indirectorClassName + "()";
+    }
+
+    protected IndirectingSerializableExtension()
+    {}
+
+    public Collection extraSpecificImports()
+    {
+	Collection col = super.extraSpecificImports();
+	col.add( indirectorClassName );
+	col.add( "com.mchange.v2.ser.IndirectlySerialized" );
+	col.add( "com.mchange.v2.ser.Indirector" );
+	col.add( "com.mchange.v2.ser.SerializableUtils" );
+	col.add( "java.io.NotSerializableException" );
+	return col;
+    }
+
+    protected IndirectPolicy indirectingPolicy( Property prop, Class propType )
+    {
+	if (Serializable.class.isAssignableFrom( propType ))
+	    return IndirectPolicy.DEFINITELY_DIRECT;
+	else
+	    return IndirectPolicy.INDIRECT_ON_EXCEPTION;
+    }
+
+    /**
+     * hook method... does nothing by default... override at will.
+     * The indirector will be called, uh, "indirector".
+     * You are in the middle of a method when you define this.
+     */
+    protected void writeInitializeIndirector( Property prop, Class propType, IndentedWriter iw ) throws IOException
+    {}
+
+    protected void writeExtraDeclarations(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {}
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	super.generate( info, superclassType, props, propTypes, iw);
+	writeExtraDeclarations( info, superclassType, props, propTypes, iw);
+    }
+
+    protected void writeStoreObject( Property prop, Class propType, IndentedWriter iw ) throws IOException
+    {
+	IndirectPolicy policy = indirectingPolicy( prop, propType );
+	if (policy == IndirectPolicy.DEFINITELY_INDIRECT)
+	    writeIndirectStoreObject( prop, propType, iw );
+	else if (policy == IndirectPolicy.INDIRECT_ON_EXCEPTION)
+	    {
+		iw.println("try");
+		iw.println("{");
+		iw.upIndent();
+		iw.println("//test serialize");
+		iw.println("SerializableUtils.toByteArray(" + prop.getName() + ");");
+		super.writeStoreObject( prop, propType, iw );
+		iw.downIndent();
+		iw.println("}");
+		iw.println("catch (NotSerializableException nse)");
+		iw.println("{");
+		iw.upIndent();
+		writeIndirectStoreObject( prop, propType, iw );
+		iw.downIndent();
+		iw.println("}");
+	    }
+	else if (policy == IndirectPolicy.DEFINITELY_DIRECT)
+	    super.writeStoreObject( prop, propType, iw );
+	else
+	    throw new InternalError("indirectingPolicy() overridden to return unknown policy: " + policy);
+    }
+
+    protected void writeIndirectStoreObject( Property prop, Class propType, IndentedWriter iw ) throws IOException
+    {
+	iw.println("try");
+	iw.println("{");
+	iw.upIndent();
+
+	iw.println("Indirector indirector = " + findIndirectorExpr + ';');
+	writeInitializeIndirector( prop, propType, iw );
+	iw.println("oos.writeObject( indirector.indirectForm( " + prop.getName() + " ) );");
+
+	iw.downIndent();
+	iw.println("}");
+	iw.println("catch (IOException indirectionIOException)");
+	iw.println("{ throw indirectionIOException; }");
+	iw.println("catch (Exception indirectionOtherException)");
+	iw.println("{ throw new IOException(\"Problem indirectly serializing " + prop.getName() + ": \" + indirectionOtherException.toString() ); }");
+    }
+
+    protected void writeUnstoreObject( Property prop, Class propType, IndentedWriter iw ) throws IOException
+    {
+	IndirectPolicy policy = indirectingPolicy( prop, propType );
+	if (policy == IndirectPolicy.DEFINITELY_INDIRECT || policy == IndirectPolicy.INDIRECT_ON_EXCEPTION)
+	    {
+		iw.println("Object o = ois.readObject();");
+		iw.println("if (o instanceof IndirectlySerialized) o = ((IndirectlySerialized) o).getObject();");
+		iw.println("this." + prop.getName() + " = (" + prop.getSimpleTypeName() + ") o;");
+	    }
+	else if (policy == IndirectPolicy.DEFINITELY_DIRECT)
+	    super.writeUnstoreObject( prop, propType, iw );
+	else
+	    throw new InternalError("indirectingPolicy() overridden to return unknown policy: " + policy);
+    }
+
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/InnerBeanPropertyBeanGenerator.java b/src/classes/com/mchange/v2/codegen/bean/InnerBeanPropertyBeanGenerator.java
new file mode 100644
index 0000000..17f8502
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/InnerBeanPropertyBeanGenerator.java
@@ -0,0 +1,200 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.lang.reflect.Modifier;
+import java.io.IOException;
+import com.mchange.v2.codegen.*;
+
+public class InnerBeanPropertyBeanGenerator extends SimplePropertyBeanGenerator
+{
+    String innerBeanClassName;
+
+    int inner_bean_member_modifiers = Modifier.PROTECTED;
+
+    int inner_bean_accessor_modifiers = Modifier.PROTECTED;
+    int inner_bean_replacer_modifiers = Modifier.PROTECTED;
+
+    String innerBeanInitializationExpression = null; //if you want it to stay null, set to String "null"
+
+    public void setInnerBeanClassName(String innerBeanClassName)
+    { this.innerBeanClassName = innerBeanClassName; }
+
+    public String getInnerBeanClassName()
+    { return innerBeanClassName; }
+
+    private String defaultInnerBeanInitializationExpression()
+    { return "new " + innerBeanClassName + "()"; }
+
+    private String findInnerBeanClassName()
+    { return (innerBeanClassName == null ? "InnerBean" : innerBeanClassName); }
+
+    private String findInnerBeanInitializationExpression()
+    { return (innerBeanInitializationExpression == null ? defaultInnerBeanInitializationExpression() : innerBeanInitializationExpression); }
+
+    private int findInnerClassModifiers()
+    {
+	int out = Modifier.STATIC;
+	if (Modifier.isPublic( inner_bean_accessor_modifiers ) || Modifier.isPublic( inner_bean_replacer_modifiers ))
+	    out |= Modifier.PUBLIC;
+	else if (Modifier.isProtected( inner_bean_accessor_modifiers ) || Modifier.isProtected( inner_bean_replacer_modifiers ))
+	    out |= Modifier.PROTECTED;
+	else if (Modifier.isPrivate( inner_bean_accessor_modifiers ) && Modifier.isPrivate( inner_bean_replacer_modifiers ))
+	    out |= Modifier.PRIVATE;
+	//else leave as package accessible
+	return out;
+    }
+
+
+    //TODO: add a hook for subclassses to custom define maskedProps
+    private void writeSyntheticInnerBeanClass() throws IOException
+    {
+	int num_props = props.length;
+	Property[] maskedProps = new Property[ num_props ];
+	for (int i = 0; i < num_props; ++i)
+	    {
+		maskedProps[i] = new SimplePropertyMask( props[i] )
+		    {
+			public int getVariableModifiers()
+			{ return Modifier.PRIVATE | Modifier.TRANSIENT; }
+		    };
+	    }
+
+	ClassInfo ci = new WrapperClassInfo( info )
+	    {
+		public String getClassName()
+		{ return "InnerBean"; }
+		
+		public int getModifiers()
+		{ return findInnerClassModifiers(); }
+	    };
+
+	createInnerGenerator().generate( ci, maskedProps, iw );
+    }
+
+    protected PropertyBeanGenerator createInnerGenerator()
+    {
+	SimplePropertyBeanGenerator innerGenerator = new SimplePropertyBeanGenerator();
+	innerGenerator.setInner( true ); 
+	innerGenerator.addExtension( new SerializableExtension() );
+	CloneableExtension ce = new CloneableExtension();
+	ce.setExceptionSwallowing( true );
+	innerGenerator.addExtension( ce );
+	return innerGenerator;
+    }
+
+    protected void writeOtherVariables() throws IOException
+    {
+	iw.println(  CodegenUtils.getModifierString( inner_bean_member_modifiers ) + ' ' +
+		     findInnerBeanClassName() + " innerBean = " + findInnerBeanInitializationExpression() + ';');
+	iw.println();
+	iw.println( CodegenUtils.getModifierString( inner_bean_accessor_modifiers ) + ' ' +
+		    findInnerBeanClassName() + " accessInnerBean()");
+	iw.println("{ return innerBean; }");
+    }
+
+    protected void writeOtherFunctions() throws IOException
+    {
+	iw.print( CodegenUtils.getModifierString( inner_bean_replacer_modifiers ) + ' ' +
+		  findInnerBeanClassName() + " replaceInnerBean( " + findInnerBeanClassName() + " innerBean )");
+	if (constrainedProperties())
+	    iw.println(" throws PropertyVetoException");
+	else
+	    iw.println();
+	iw.println("{");
+	iw.upIndent();
+	iw.println("beforeReplaceInnerBean();");
+	iw.println("this.innerBean = innerBean;");
+	iw.println("afterReplaceInnerBean();");
+	iw.downIndent();
+	iw.println("}");
+	iw.println();
+
+	boolean is_abstract = Modifier.isAbstract( info.getModifiers() );
+	iw.print("protected ");
+	if (is_abstract)
+	    iw.print("abstract ");
+	iw.print("void beforeReplaceInnerBean()");
+	if (constrainedProperties())
+	    iw.print(" throws PropertyVetoException");
+	if (is_abstract)
+	    iw.println(';');
+	else
+	    iw.println(" {} //hook method for subclasses");
+	iw.println();
+
+	iw.print("protected ");
+	if (is_abstract)
+	    iw.print("abstract ");
+	iw.print("void afterReplaceInnerBean()");
+	if (is_abstract)
+	    iw.println(';');
+	else
+	    iw.println(" {} //hook method for subclasses");
+	iw.println();
+
+	BeangenUtils.writeExplicitDefaultConstructor( Modifier.PUBLIC, info, iw );
+	iw.println();
+	iw.println("public " + info.getClassName() + "(" + findInnerBeanClassName() + " innerBean)");
+	iw.println("{ this.innerBean = innerBean; }");
+    }
+
+    protected void writeOtherClasses() throws IOException
+    {
+	if (innerBeanClassName == null)
+	    writeSyntheticInnerBeanClass();
+    }
+
+    protected void writePropertyVariable( Property prop ) throws IOException
+    { /* do nothing... we have no members, only the inner bean */ }
+
+    protected void writePropertyGetter( Property prop, Class propType ) throws IOException
+    { 
+	String stn = prop.getSimpleTypeName();
+	String pfx = ("boolean".equals( stn )  ? "is" : "get" );
+	String methodName = pfx + BeangenUtils.capitalize( prop.getName() );
+	iw.print( CodegenUtils.getModifierString( prop.getGetterModifiers() ) );
+	iw.println(' ' + prop.getSimpleTypeName() + ' ' + methodName + "()");
+	iw.println('{');
+	iw.upIndent();
+	iw.println( stn + ' ' +  prop.getName() + " = innerBean." + methodName + "();"); 
+	String retVal = this.getGetterDefensiveCopyExpression( prop, propType ); 
+	if (retVal == null) retVal = prop.getName();
+	iw.println("return " + retVal + ';');
+	iw.downIndent();
+	iw.println('}');
+    }
+
+    protected void writePropertySetter( Property prop, Class propType ) throws IOException
+    {
+	String stn = prop.getSimpleTypeName();
+	String pfx = ("boolean".equals( stn )  ? "is" : "get" );
+
+	String setVal = this.getSetterDefensiveCopyExpression( prop, propType );
+	if (setVal == null) setVal = prop.getName();
+	String getExpression = ("innerBean." + pfx + BeangenUtils.capitalize( prop.getName() ) + "()");
+	String setStatement = ("innerBean.set" + BeangenUtils.capitalize( prop.getName() ) + "( " + setVal + " );");
+	BeangenUtils.writePropertySetterWithGetExpressionSetStatement(prop, getExpression, setStatement, iw);
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/codegen/bean/ParsedPropertyBeanDocument.java b/src/classes/com/mchange/v2/codegen/bean/ParsedPropertyBeanDocument.java
new file mode 100644
index 0000000..d478c97
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/ParsedPropertyBeanDocument.java
@@ -0,0 +1,180 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import org.w3c.dom.*;
+import java.lang.reflect.Modifier;
+import com.mchange.v1.xml.DomParseUtils;
+
+public class ParsedPropertyBeanDocument
+{
+    final static String[] EMPTY_SA = new String[0];
+
+    String   packageName;
+    int      class_modifiers;
+    String   className;
+    String   superclassName;
+    String[] interfaceNames  = EMPTY_SA;
+    String[] generalImports  = EMPTY_SA;
+    String[] specificImports = EMPTY_SA;
+    Property[] properties;
+    
+    public ParsedPropertyBeanDocument(Document doc)
+    {
+	Element rootElem = doc.getDocumentElement();
+        this.packageName = DomParseUtils.allTextFromUniqueChild( rootElem, "package" );
+	Element modifiersElem = DomParseUtils.uniqueImmediateChild( rootElem, "modifiers" );
+	if (modifiersElem != null)
+	    class_modifiers = parseModifiers( modifiersElem );
+	else
+	    class_modifiers = Modifier.PUBLIC;
+	
+	Element importsElem = DomParseUtils.uniqueChild( rootElem, "imports" );
+	if (importsElem != null)
+	    {
+		this.generalImports = DomParseUtils.allTextFromImmediateChildElements( importsElem, "general" );
+		this.specificImports = DomParseUtils.allTextFromImmediateChildElements( importsElem, "specific" );
+	    }
+	this.className = DomParseUtils.allTextFromUniqueChild( rootElem, "output-class" ); 
+	this.superclassName = DomParseUtils.allTextFromUniqueChild( rootElem, "extends" ); 
+
+	Element implementsElem = DomParseUtils.uniqueChild( rootElem, "implements" );
+	if (implementsElem != null)
+	    this.interfaceNames = DomParseUtils.allTextFromImmediateChildElements( implementsElem, "interface" );
+	Element propertiesElem = DomParseUtils.uniqueChild( rootElem, "properties" );
+	this.properties = findProperties( propertiesElem );
+    }
+
+
+    public ClassInfo getClassInfo()
+    {
+	return new ClassInfo()
+	    {
+		public String getPackageName()
+		{ return packageName; }
+
+		public int getModifiers()
+		{ return class_modifiers; }
+
+		public String getClassName()
+		{ return className; }
+
+		public String getSuperclassName()
+		{ return superclassName; }
+
+		public String[] getInterfaceNames()
+		{ return interfaceNames; }
+
+		public String[] getGeneralImports()
+		{ return generalImports; }
+
+		public String[] getSpecificImports()
+		{ return specificImports; }
+	    };
+    }
+
+    public Property[] getProperties()
+    { return (Property[]) properties.clone(); }
+
+    private Property[] findProperties( Element propertiesElem )
+    {
+	NodeList nl = DomParseUtils.immediateChildElementsByTagName( propertiesElem, "property" );
+	int len = nl.getLength();
+	Property[] out = new Property[ len ];
+	for( int i = 0; i < len; ++i)
+	    {
+		Element propertyElem = (Element) nl.item( i );
+
+		int variable_modifiers;
+		String name;
+		String simpleTypeName;
+		String  defensiveCopyExpression;
+		String  defaultValueExpression;
+		int     getter_modifiers;
+		int     setter_modifiers;
+		boolean is_read_only;
+		boolean is_bound;
+		boolean is_constrained;
+		
+		variable_modifiers = modifiersThroughParentElem( propertyElem, "variable", Modifier.PRIVATE );
+		name = DomParseUtils.allTextFromUniqueChild( propertyElem, "name", true );
+		simpleTypeName = DomParseUtils.allTextFromUniqueChild( propertyElem, "type", true );
+		defensiveCopyExpression = DomParseUtils.allTextFromUniqueChild( propertyElem, "defensive-copy", true );
+		defaultValueExpression = DomParseUtils.allTextFromUniqueChild( propertyElem, "default-value", true );
+		getter_modifiers = modifiersThroughParentElem( propertyElem, "getter", Modifier.PUBLIC );
+		setter_modifiers = modifiersThroughParentElem( propertyElem, "setter", Modifier.PUBLIC );
+		Element readOnlyElem = DomParseUtils.uniqueChild( propertyElem, "read-only" );
+		is_read_only = (readOnlyElem != null);
+		Element isBoundElem = DomParseUtils.uniqueChild( propertyElem, "bound" );
+		is_bound = (isBoundElem != null);
+		Element isConstrainedElem = DomParseUtils.uniqueChild( propertyElem, "constrained" );
+		is_constrained = (isConstrainedElem != null);
+		out[i] = new SimpleProperty( variable_modifiers, name, simpleTypeName, defensiveCopyExpression, 
+					     defaultValueExpression, getter_modifiers, setter_modifiers, 
+					     is_read_only, is_bound, is_constrained );
+	    }
+	return out;
+    }
+
+    private static int modifiersThroughParentElem( Element grandparentElem, String parentElemName, int default_mods )
+    {
+	Element parentElem = DomParseUtils.uniqueChild( grandparentElem, parentElemName );
+	if (parentElem != null )
+	    {
+		Element modifiersElem = DomParseUtils.uniqueChild( parentElem, "modifiers" );
+		if (modifiersElem != null)
+		    return parseModifiers( modifiersElem );
+		else
+		    return default_mods;
+	    }
+	else
+	    return default_mods;
+    }
+
+    private static int parseModifiers( Element modifiersElem )
+    {
+	int out = 0;
+	String[] all_modifiers = DomParseUtils.allTextFromImmediateChildElements( modifiersElem, "modifier", true );
+	for ( int i = 0, len = all_modifiers.length; i < len; ++i)
+	    {
+		String modifier = all_modifiers[i];
+		if ("public".equals( modifier )) out |= Modifier.PUBLIC;
+		else if ("protected".equals( modifier )) out |= Modifier.PROTECTED;
+		else if ("private".equals( modifier )) out |= Modifier.PRIVATE;
+		else if ("final".equals( modifier )) out |= Modifier.FINAL;
+		else if ("abstract".equals( modifier )) out |= Modifier.ABSTRACT;
+		else if ("static".equals( modifier )) out |= Modifier.STATIC;
+		else if ("synchronized".equals( modifier )) out |= Modifier.SYNCHRONIZED;
+		else if ("volatile".equals( modifier )) out |= Modifier.VOLATILE;
+		else if ("transient".equals( modifier )) out |= Modifier.TRANSIENT;
+		else if ("strictfp".equals( modifier )) out |= Modifier.STRICT;
+		else if ("native".equals( modifier )) out |= Modifier.NATIVE;
+		else if ("interface".equals( modifier )) out |= Modifier.INTERFACE; // ????
+		else throw new IllegalArgumentException("Bad modifier: " + modifier);
+	    }
+	return out;
+    }
+
+
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/Property.java b/src/classes/com/mchange/v2/codegen/bean/Property.java
new file mode 100644
index 0000000..e8bd671
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/Property.java
@@ -0,0 +1,40 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+public interface Property
+{
+    public int     getVariableModifiers();
+    public String  getName();
+    public String  getSimpleTypeName();
+    public String  getDefensiveCopyExpression();
+    public String  getDefaultValueExpression();
+    public int     getGetterModifiers();
+    public int     getSetterModifiers();
+    public boolean isReadOnly();
+    public boolean isBound();
+    public boolean isConstrained();
+
+    //indexed properties not supported... use array or List valued props
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/PropertyBeanGenerator.java b/src/classes/com/mchange/v2/codegen/bean/PropertyBeanGenerator.java
new file mode 100644
index 0000000..a8e3747
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/PropertyBeanGenerator.java
@@ -0,0 +1,31 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.io.*;
+
+public interface PropertyBeanGenerator
+{
+    public void generate( ClassInfo info, Property[] props, Writer w ) throws IOException;
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/PropertyComparator.java b/src/classes/com/mchange/v2/codegen/bean/PropertyComparator.java
new file mode 100644
index 0000000..f8cb806
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/PropertyComparator.java
@@ -0,0 +1,35 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+class PropertyComparator
+{
+    public int compare(Object a, Object b)
+    {
+	Property aa = (Property) a;
+	Property bb = (Property) b;
+
+	return (aa.getName().compareTo(bb.getName()));
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/PropertyMapConstructorGeneratorExtension.java b/src/classes/com/mchange/v2/codegen/bean/PropertyMapConstructorGeneratorExtension.java
new file mode 100644
index 0000000..6aa9e95
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/PropertyMapConstructorGeneratorExtension.java
@@ -0,0 +1,95 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+
+import java.lang.reflect.Modifier;
+import java.io.IOException;
+import com.mchange.v2.codegen.CodegenUtils;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public class PropertyMapConstructorGeneratorExtension implements GeneratorExtension 
+{
+    int ctor_modifiers = Modifier.PUBLIC;
+
+    public Collection extraGeneralImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraSpecificImports()
+    {
+	Set set = new HashSet();
+	set.add("java.util.Map");
+	return set;
+    }
+
+    public Collection extraInterfaceNames()
+    { return Collections.EMPTY_SET; }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	iw.print( CodegenUtils.getModifierString( ctor_modifiers ) );
+	iw.print(' ' + info.getClassName() + "( Map map )");
+	iw.println("{");
+	iw.upIndent();
+
+	iw.println( "Object raw;" );
+	for (int i = 0, len = props.length; i < len; ++i)
+	    {
+		Property prop   = props[i];
+		String propName = prop.getName();
+		Class propType  = propTypes[i];
+		iw.println("raw = map.get( \"" + propName + "\" );");
+		iw.println("if (raw != null)");
+		iw.println("{");
+		iw.upIndent();
+
+		iw.print("this." + propName + " = ");
+		if ( propType == boolean.class )
+		    iw.println( "((Boolean) raw ).booleanValue();" );
+		else if ( propType == byte.class )
+		    iw.println( "((Byte) raw ).byteValue();" );
+		else if ( propType == char.class )
+		    iw.println( "((Character) raw ).charValue();" );
+		else if ( propType == short.class )
+		    iw.println( "((Short) raw ).shortValue();" );
+		else if ( propType == int.class )
+		    iw.println( "((Integer) raw ).intValue();" );
+		else if ( propType == long.class )
+		    iw.println( "((Long) raw ).longValue();" );
+		else if ( propType == float.class )
+		    iw.println( "((Float) raw ).floatValue();" );
+		else if ( propType == double.class )
+		    iw.println( "((Double) raw ).doubleValue();" );
+		iw.println("raw = null;");
+
+		iw.downIndent();
+		iw.println("}");
+	    }
+
+	iw.downIndent();
+	iw.println("}");
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/PropertyReferenceableExtension.java b/src/classes/com/mchange/v2/codegen/bean/PropertyReferenceableExtension.java
new file mode 100644
index 0000000..06479f3
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/PropertyReferenceableExtension.java
@@ -0,0 +1,112 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import com.mchange.v2.codegen.IndentedWriter;
+import com.mchange.v2.naming.JavaBeanObjectFactory;
+import com.mchange.v2.naming.JavaBeanReferenceMaker;
+
+import java.io.IOException;
+
+public class PropertyReferenceableExtension implements GeneratorExtension
+{
+    boolean explicit_reference_properties = false;
+
+    String factoryClassName = JavaBeanObjectFactory.class.getName();
+
+    String javaBeanReferenceMakerClassName = JavaBeanReferenceMaker.class.getName();
+
+    public void setUseExplicitReferenceProperties( boolean explicit_reference_properties )
+    { this.explicit_reference_properties = explicit_reference_properties; }
+
+    public boolean getUseExplicitReferenceProperties()
+    { return explicit_reference_properties; }
+
+    public void setFactoryClassName( String factoryClassName )
+    { this.factoryClassName = factoryClassName; }
+
+    public String getFactoryClassName()
+    { return factoryClassName; }
+
+//     public void setJavaBeanReferenceMakerClassName( String javaBeanReferenceMakerClassName )
+//     { this.javaBeanReferenceMakerClassName = javaBeanReferenceMakerClassName; }
+
+//     public String getJavaBeanReferenceMakerClassName()
+//     { return javaBeanReferenceMakerClassName; }
+
+    public Collection extraGeneralImports()
+    { 
+	Set set = new HashSet();
+	return set;
+    }
+
+    public Collection extraSpecificImports()
+    {
+	Set set = new HashSet();
+	set.add( "javax.naming.Reference" );
+	set.add( "javax.naming.Referenceable" );
+	set.add( "javax.naming.NamingException" );
+	set.add( "com.mchange.v2.naming.JavaBeanObjectFactory" );
+	set.add( "com.mchange.v2.naming.JavaBeanReferenceMaker" );
+	set.add( "com.mchange.v2.naming.ReferenceMaker" );
+	return set;
+    }
+
+    public Collection extraInterfaceNames()
+    {
+	Set set = new HashSet();
+	set.add( "Referenceable" );
+	return set;
+    }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	iw.println("final static JavaBeanReferenceMaker referenceMaker = new " + javaBeanReferenceMakerClassName + "();");
+	iw.println();
+	iw.println("static"); 
+	iw.println("{"); 
+	iw.upIndent();
+	
+	iw.println("referenceMaker.setFactoryClassName( \"" + factoryClassName + "\" );");
+	if ( explicit_reference_properties )
+	    {
+		for( int i = 0, len = props.length; i < len; ++i)
+		    iw.println("referenceMaker.addReferenceProperty(\"" + props[i].getName() + "\");");
+	    }
+
+	iw.downIndent();
+	iw.println("}");
+	iw.println();
+	iw.println("public Reference getReference() throws NamingException");
+	iw.println("{"); 
+	iw.upIndent();
+	
+	iw.println("return referenceMaker.createReference( this );");
+
+	iw.downIndent();
+	iw.println("}");
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/PropsToStringGeneratorExtension.java b/src/classes/com/mchange/v2/codegen/bean/PropsToStringGeneratorExtension.java
new file mode 100644
index 0000000..293f4e0
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/PropsToStringGeneratorExtension.java
@@ -0,0 +1,89 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+
+import java.io.IOException;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public class PropsToStringGeneratorExtension implements GeneratorExtension 
+{
+    private Collection excludePropNames = null;
+
+    public void setExcludePropertyNames( Collection excludePropNames )
+    { this.excludePropNames = excludePropNames; }
+
+    public Collection getExcludePropertyNames()
+    { return excludePropNames; }
+
+    public Collection extraGeneralImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraSpecificImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraInterfaceNames()
+    { return Collections.EMPTY_SET; }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	iw.println("public String toString()");
+	iw.println("{");
+	iw.upIndent();
+
+	iw.println("StringBuffer sb = new StringBuffer();");
+	iw.println("sb.append( super.toString() );");
+	iw.println("sb.append(\" [ \");");
+
+	for (int i = 0, len = props.length; i < len; ++i)
+	    {
+		Property prop = props[i];
+
+		if ( excludePropNames != null && excludePropNames.contains( prop.getName() ) )
+		    continue;
+
+		iw.println("sb.append( \"" + prop.getName() + " -> \"" + " + " + prop.getName() + " );");
+		if ( i != len - 1 )
+		    iw.println("sb.append( \", \");");
+	    }
+
+	iw.println();
+	iw.println("String extraToStringInfo = this.extraToStringInfo();");
+	iw.println("if (extraToStringInfo != null)");
+	iw.upIndent();
+	iw.println("sb.append( extraToStringInfo );");
+	iw.downIndent();
+
+
+	iw.println("sb.append(\" ]\");");
+	iw.println("return sb.toString();");
+	iw.downIndent();
+	iw.println("}");
+	iw.println();
+	iw.println("protected String extraToStringInfo()");
+	iw.println("{ return null; }");
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/ResolvedClassInfo.java b/src/classes/com/mchange/v2/codegen/bean/ResolvedClassInfo.java
new file mode 100644
index 0000000..641872b
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/ResolvedClassInfo.java
@@ -0,0 +1,30 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+public interface ResolvedClassInfo extends ClassInfo
+{
+    public Class[] getInterfaces();
+    public Class[] getSuperclass();
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/ResolvedProperty.java b/src/classes/com/mchange/v2/codegen/bean/ResolvedProperty.java
new file mode 100644
index 0000000..c311fce
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/ResolvedProperty.java
@@ -0,0 +1,29 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+public interface ResolvedProperty extends Property
+{
+    public Class getType();
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/SerializableExtension.java b/src/classes/com/mchange/v2/codegen/bean/SerializableExtension.java
new file mode 100644
index 0000000..b15087d
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/SerializableExtension.java
@@ -0,0 +1,201 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import java.io.IOException;
+import com.mchange.v2.codegen.IndentedWriter;
+
+
+/**
+ *  Note: this class pays no attention to whether users have marked any property variables as transient.
+ *  In fact, it will work most efficiently if users mark ALL variables as transient... to define transient
+ *  properties for this class, use the constructor which allows a user-specified set of transients.
+ */
+public class SerializableExtension implements GeneratorExtension
+{
+    Set transientProperties;
+    Map transientPropertyInitializers;
+
+    /**
+     *  @param transientProperties a set of Strings, the names of all properties that should be considered transient and not serialized
+     *  @param transientPropertyInitializers an optional Map of a subset of the transient property names to non-default initialization
+     *                                       expressions, which should be unterminated expressions, and which will be used verbatim in 
+     *                                       the generated code.
+     */
+    public SerializableExtension(Set transientProperties, Map transientPropertyInitializers)
+    { 
+	this.transientProperties = transientProperties; 
+	this.transientPropertyInitializers = transientPropertyInitializers;
+    }
+
+    public SerializableExtension()
+    { this ( Collections.EMPTY_SET, null ); }
+
+
+    public Collection extraGeneralImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraSpecificImports()
+    {
+	Set set = new HashSet();
+	set.add( "java.io.IOException" );
+	set.add( "java.io.Serializable" );
+	set.add( "java.io.ObjectOutputStream" );
+	set.add( "java.io.ObjectInputStream" );
+	return set;
+    }
+
+    public Collection extraInterfaceNames()
+    {
+	Set set = new HashSet();
+	set.add( "Serializable" );
+	return set;
+    }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	iw.println("private static final long serialVersionUID = 1;"); 
+	iw.println("private static final short VERSION = 0x0001;"); 
+	iw.println();
+	iw.println("private void writeObject( ObjectOutputStream oos ) throws IOException");
+	iw.println("{");
+	iw.upIndent();
+	
+	iw.println( "oos.writeShort( VERSION );" );
+
+	for( int i = 0, len = props.length; i < len; ++i )
+	    {
+		Property prop = props[i];
+		if (! transientProperties.contains( prop.getName() ) )
+		    {
+			Class propType = propTypes[i];
+			if (propType != null && propType.isPrimitive()) //primitives should always resolve, object types may not, and be null
+			    {
+				if (propType == byte.class)
+				    iw.println("oos.writeByte(" + prop.getName() + ");");
+				else if (propType == char.class)
+				    iw.println("oos.writeChar(" + prop.getName() + ");");
+				else if (propType == short.class)
+				    iw.println("oos.writeShort(" + prop.getName() + ");");
+				else if (propType == int.class)
+				    iw.println("oos.writeInt(" + prop.getName() + ");");
+				else if (propType == boolean.class)
+				    iw.println("oos.writeBoolean(" + prop.getName() + ");");
+				else if (propType == long.class)
+				    iw.println("oos.writeLong(" + prop.getName() + ");");
+				else if (propType == float.class)
+				    iw.println("oos.writeFloat(" + prop.getName() + ");");
+				else if (propType == double.class)
+				    iw.println("oos.writeDouble(" + prop.getName() + ");");
+			    }
+			else
+			    writeStoreObject( prop, propType, iw );
+		    }
+	    }
+	generateExtraSerWriteStatements( info, superclassType, props, propTypes, iw);
+	iw.downIndent();
+	iw.println("}");
+	iw.println();
+
+	iw.println("private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException");
+	iw.println("{");
+	iw.upIndent();
+	iw.println("short version = ois.readShort();");
+	iw.println("switch (version)");
+	iw.println("{");
+	iw.upIndent();
+	
+	iw.println("case VERSION:");
+	iw.upIndent();
+	for( int i = 0, len = props.length; i < len; ++i )
+	    {
+		Property prop = props[i];
+		if (! transientProperties.contains( prop.getName() ) )
+		    {
+			Class propType = propTypes[i];
+			if (propType != null && propType.isPrimitive()) //if a propType is unresolvable, it ain't a primitive
+			    {
+				if (propType == byte.class)
+				    iw.println("this." + prop.getName() + " = ois.readByte();");
+				else if (propType == char.class)
+				    iw.println("this." + prop.getName() + " = ois.readChar();");
+				else if (propType == short.class)
+				    iw.println("this." + prop.getName() + " = ois.readShort();");
+				else if (propType == int.class)
+				    iw.println("this." + prop.getName() + " = ois.readInt();");
+				else if (propType == boolean.class)
+				    iw.println("this." + prop.getName() + " = ois.readBoolean();");
+				else if (propType == long.class)
+				    iw.println("this." + prop.getName() + " = ois.readLong();");
+				else if (propType == float.class)
+				    iw.println("this." + prop.getName() + " = ois.readFloat();");
+				else if (propType == double.class)
+				    iw.println("this." + prop.getName() + " = ois.readDouble();");
+			    }
+			else
+			    writeUnstoreObject( prop, propType, iw );
+		    }
+		else
+		    {
+			String initializer = (String) transientPropertyInitializers.get( prop.getName() );
+			if (initializer != null)
+			    iw.println("this." + prop.getName() + " = " + initializer +';');
+		    }
+	    }
+	generateExtraSerInitializers( info, superclassType, props, propTypes, iw);
+	iw.println("break;");
+	iw.downIndent();
+	iw.println("default:");
+	iw.upIndent();
+	iw.println("throw new IOException(\"Unsupported Serialized Version: \" + version);");
+	iw.downIndent();
+
+	iw.downIndent();
+	iw.println("}");
+
+	iw.downIndent();
+	iw.println("}");
+    }
+
+    protected void writeStoreObject( Property prop, Class propType, IndentedWriter iw ) throws IOException
+    {
+	iw.println("oos.writeObject( " + prop.getName() + " );");
+    }
+
+    protected void writeUnstoreObject( Property prop, Class propType, IndentedWriter iw ) throws IOException
+    {
+	iw.println("this." + prop.getName() + " = (" + prop.getSimpleTypeName() + ") ois.readObject();");
+    }
+
+    protected void generateExtraSerWriteStatements(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {}
+
+    protected void generateExtraSerInitializers(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {}
+
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/SimpleClassInfo.java b/src/classes/com/mchange/v2/codegen/bean/SimpleClassInfo.java
new file mode 100644
index 0000000..03be360
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/SimpleClassInfo.java
@@ -0,0 +1,62 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.lang.reflect.Modifier;
+
+public class SimpleClassInfo implements ClassInfo
+{
+    String   packageName;
+    int      modifiers;
+    String   className;
+    String   superclassName;
+    String[] interfaceNames;
+    String[] generalImports;
+    String[] specificImports;
+
+    public String   getPackageName()          { return packageName; }
+    public int      getModifiers()            { return modifiers; }
+    public String   getClassName()            { return className; }
+    public String   getSuperclassName()       { return superclassName; }
+    public String[] getInterfaceNames()       { return interfaceNames; }
+    public String[] getGeneralImports()       { return generalImports; }
+    public String[] getSpecificImports()      { return specificImports; }
+
+    public SimpleClassInfo( String   packageName,
+			    int      modifiers,
+			    String   className,
+			    String   superclassName,
+			    String[] interfaceNames,
+			    String[] generalImports,
+			    String[] specificImports )
+    {
+	this.packageName = packageName;
+	this.modifiers = modifiers;
+	this.className = className;
+	this.superclassName = superclassName;
+	this.interfaceNames = interfaceNames;
+	this.generalImports = generalImports;
+	this.specificImports = specificImports;
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/SimpleProperty.java b/src/classes/com/mchange/v2/codegen/bean/SimpleProperty.java
new file mode 100644
index 0000000..e15a108
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/SimpleProperty.java
@@ -0,0 +1,94 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.lang.reflect.Modifier;
+
+public class SimpleProperty implements Property
+{
+    int     variable_modifiers;
+    String  name;
+    String  simpleTypeName;
+    String  defensiveCopyExpression;
+    String  defaultValueExpression;
+    int     getter_modifiers;
+    int     setter_modifiers;
+    boolean is_read_only;
+    boolean is_bound;
+    boolean is_constrained;
+
+    public int     getVariableModifiers()       { return variable_modifiers; }
+    public String  getName()                    { return name; }
+    public String  getSimpleTypeName()          { return simpleTypeName; }
+    public String  getDefensiveCopyExpression() { return defensiveCopyExpression; }
+    public String  getDefaultValueExpression()  { return defaultValueExpression; }
+    public int     getGetterModifiers()         { return getter_modifiers; }
+    public int     getSetterModifiers()         { return setter_modifiers; }
+    public boolean isReadOnly()                 { return is_read_only; }
+    public boolean isBound()                    { return is_bound; }
+    public boolean isConstrained()              { return is_constrained; }
+
+    public SimpleProperty( int     variable_modifiers,
+			   String  name,
+			   String  simpleTypeName,
+			   String  defensiveCopyExpression,
+			   String  defaultValueExpression,
+			   int     getter_modifiers,
+			   int     setter_modifiers,
+			   boolean is_read_only,
+			   boolean is_bound,
+			   boolean is_constrained )
+    {
+	this.variable_modifiers = variable_modifiers;
+	this.name = name;
+	this.simpleTypeName = simpleTypeName;
+	this.defensiveCopyExpression = defensiveCopyExpression;
+	this.defaultValueExpression = defaultValueExpression;
+	this.getter_modifiers = getter_modifiers;
+	this.setter_modifiers = setter_modifiers;
+	this.is_read_only = is_read_only;
+	this.is_bound = is_bound;
+	this.is_constrained = is_constrained;
+    }
+
+    public SimpleProperty( String  name,
+			   String  simpleTypeName,
+			   String  defensiveCopyExpression,
+			   String  defaultValueExpression,
+			   boolean is_read_only,
+			   boolean is_bound,
+			   boolean is_constrained )
+    {
+	this ( Modifier.PRIVATE,
+	       name,
+	       simpleTypeName,
+	       defensiveCopyExpression,
+	       defaultValueExpression,
+	       Modifier.PUBLIC,
+	       Modifier.PUBLIC,
+	       is_read_only,
+	       is_bound,
+	       is_constrained );
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/SimplePropertyBeanGenerator.java b/src/classes/com/mchange/v2/codegen/bean/SimplePropertyBeanGenerator.java
new file mode 100644
index 0000000..ed3d84e
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/SimplePropertyBeanGenerator.java
@@ -0,0 +1,610 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.io.*;
+import java.util.*;
+import com.mchange.v2.log.*;
+import java.lang.reflect.Modifier;
+import com.mchange.v1.lang.ClassUtils;
+import com.mchange.v2.codegen.CodegenUtils;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public class SimplePropertyBeanGenerator implements PropertyBeanGenerator
+{
+    private final static MLogger logger = MLog.getLogger( SimplePropertyBeanGenerator.class );
+
+    private boolean inner              = false;
+    private int     java_version       = 130; //1.3.0
+    private boolean force_unmodifiable = false;
+    private String  generatorName      = this.getClass().getName();
+
+    // helper vars for generate method
+    protected ClassInfo      info;
+    protected Property[]     props;
+    protected IndentedWriter iw;
+
+    protected Set generalImports;
+    protected Set specificImports;
+    protected Set interfaceNames;
+
+    protected Class   superclassType;
+    protected List    interfaceTypes;
+    protected Class[] propertyTypes;
+
+    protected List generatorExtensions = new ArrayList();
+
+    public synchronized void setInner( boolean inner )
+    { this.inner = inner; }
+
+    public synchronized boolean isInner()
+    { return inner; }
+
+    /**
+     * @param version a three digit number -- for example Java 1.3.1 is 131
+     */
+    public synchronized void setJavaVersion(int java_version) 
+    { this.java_version = java_version; }
+
+    public synchronized int getJavaVersion()
+    { return java_version; }
+
+    public synchronized void setGeneratorName(String generatorName) 
+    { this.generatorName = generatorName; }
+
+    public synchronized String getGeneratorName()
+    { return generatorName; }
+
+    public synchronized void setForceUnmodifiable(boolean force_unmodifiable)
+    { this.force_unmodifiable = force_unmodifiable; }
+
+    public synchronized boolean isForceUnmodifiable()
+    { return force_unmodifiable; }
+
+    public synchronized void addExtension( GeneratorExtension ext )
+    { generatorExtensions.add( ext ); }
+
+    public synchronized void removeExtension( GeneratorExtension ext )
+    { generatorExtensions.remove( ext ); }
+
+    public synchronized void generate( ClassInfo info, Property[] props, Writer w) throws IOException
+    {
+	this.info = info;
+	this.props = props;
+	Arrays.sort( props, BeangenUtils.PROPERTY_COMPARATOR );
+	this.iw = ( w instanceof IndentedWriter ? (IndentedWriter) w : new IndentedWriter(w));
+
+	this.generalImports = new TreeSet();
+	if ( info.getGeneralImports() != null )
+	    generalImports.addAll( Arrays.asList( info.getGeneralImports() ) );
+
+	this.specificImports = new TreeSet();
+	if ( info.getSpecificImports() != null )
+	    specificImports.addAll( Arrays.asList( info.getSpecificImports() ) );
+
+	this.interfaceNames = new TreeSet();
+	if ( info.getInterfaceNames() != null )
+	    interfaceNames.addAll( Arrays.asList( info.getInterfaceNames() ) );
+
+	addInternalImports();
+	addInternalInterfaces();
+
+	resolveTypes();
+
+	if (! inner )
+	    {
+		writeHeader();
+		iw.println();
+	    }
+
+	writeClassDeclaration();
+	iw.println('{');
+	iw.upIndent();
+
+	writeCoreBody();
+
+	iw.downIndent();
+	iw.println('}');
+    }
+
+    protected void resolveTypes()
+    {
+	String[] gen = (String[]) generalImports.toArray( new String[ generalImports.size() ] );
+	String[] spc = (String[]) specificImports.toArray( new String[ specificImports.size() ] );
+
+	if ( info.getSuperclassName() != null )
+	    {
+		try
+		    { superclassType = ClassUtils.forName( info.getSuperclassName(), gen, spc ); }
+		catch ( Exception e )
+		    {
+// 			System.err.println("WARNING: " + this.getClass().getName() + " could not resolve " +
+// 					   "superclass '" + info.getSuperclassName() + "'.");
+			if ( logger.isLoggable( MLevel.WARNING ) )
+			    logger.warning(this.getClass().getName() + " could not resolve superclass '" + info.getSuperclassName() + "'.");
+
+			superclassType = null;
+		    }
+	    }
+
+	interfaceTypes = new ArrayList( interfaceNames.size() );
+	for ( Iterator ii = interfaceNames.iterator(); ii.hasNext(); )
+	    {
+		String name = (String) ii.next();
+		try 
+		    { interfaceTypes.add( ClassUtils.forName( name , gen, spc ) ); }
+		catch ( Exception e )
+		    {
+// 			System.err.println("WARNING: " + this.getClass().getName() + " could not resolve " +
+// 					   "interface '" + name + "'.");
+
+			if ( logger.isLoggable( MLevel.WARNING ) )
+			    logger.warning(this.getClass().getName() + " could not resolve interface '" + name + "'.");
+
+			interfaceTypes.add( null );
+		    }
+	    }
+
+	propertyTypes = new Class[ props.length ];
+	for ( int i = 0, len = props.length; i < len; ++i )
+	    {
+		String name = props[i].getSimpleTypeName();
+		try 
+		    { propertyTypes[i] = ClassUtils.forName( name , gen, spc ); }
+		catch ( Exception e )
+		    {
+// 			e.printStackTrace();
+// 			System.err.println("WARNING: " + this.getClass().getName() + " could not resolve " +
+// 					   "property type '" + name + "'.");
+
+			if ( logger.isLoggable( MLevel.WARNING ) )
+			    logger.log( MLevel.WARNING, this.getClass().getName() + " could not resolve property type '" + name + "'.", e);
+
+			propertyTypes[i] = null;
+		    }
+	    }
+    }
+
+    protected void addInternalImports()
+    {
+	if (boundProperties())
+	    {
+		specificImports.add("java.beans.PropertyChangeEvent");
+		specificImports.add("java.beans.PropertyChangeSupport");
+		specificImports.add("java.beans.PropertyChangeListener");
+	    }
+	if (constrainedProperties())
+	    {
+		specificImports.add("java.beans.PropertyChangeEvent");
+		specificImports.add("java.beans.PropertyVetoException");
+		specificImports.add("java.beans.VetoableChangeSupport");
+		specificImports.add("java.beans.VetoableChangeListener");
+	    }
+
+	for (Iterator ii = generatorExtensions.iterator(); ii.hasNext(); )
+	    {
+		GeneratorExtension ge = (GeneratorExtension) ii.next();
+		specificImports.addAll( ge.extraSpecificImports() );
+		generalImports.addAll( ge.extraGeneralImports() );
+	    }
+    }
+
+    protected void addInternalInterfaces()
+    {
+	for (Iterator ii = generatorExtensions.iterator(); ii.hasNext(); )
+	    {
+		GeneratorExtension ge = (GeneratorExtension) ii.next();
+		interfaceNames.addAll( ge.extraInterfaceNames() );
+	    }
+    }
+
+    protected void writeCoreBody() throws IOException
+    {
+	writeJavaBeansChangeSupport();
+	writePropertyVariables();
+	writeOtherVariables();
+	iw.println();
+
+	writeGetterSetterPairs();
+	if ( boundProperties() )
+	    {
+		iw.println();
+		writeBoundPropertyEventSourceMethods();
+	    }
+	if ( constrainedProperties() )
+	    {
+		iw.println();
+		writeConstrainedPropertyEventSourceMethods();
+	    }
+	writeInternalUtilityFunctions();
+	writeOtherFunctions();
+
+	writeOtherClasses();
+
+	String[] completed_intfc_names = (String[]) interfaceNames.toArray( new String[ interfaceNames.size() ] );
+	String[] completed_gen_imports = (String[]) generalImports.toArray( new String[ generalImports.size() ] );
+	String[] completed_spc_imports = (String[]) specificImports.toArray( new String[ specificImports.size() ] );
+	ClassInfo completedClassInfo = new SimpleClassInfo( info.getPackageName(),
+							    info.getModifiers(),
+							    info.getClassName(),
+							    info.getSuperclassName(),
+							    completed_intfc_names,
+							    completed_gen_imports,
+							    completed_spc_imports );
+	for (Iterator ii = generatorExtensions.iterator(); ii.hasNext(); )
+	    {
+		GeneratorExtension ext = (GeneratorExtension) ii.next();
+		iw.println();
+		ext.generate( completedClassInfo, superclassType, props, propertyTypes, iw );
+	    }
+    }
+
+    protected void writeInternalUtilityFunctions() throws IOException
+    {
+	iw.println("private boolean eqOrBothNull( Object a, Object b )");
+	iw.println("{");
+	iw.upIndent();
+
+	iw.println("return");
+	iw.upIndent();
+	iw.println("a == b ||");
+	iw.println("(a != null && a.equals(b));");
+	iw.downIndent();
+
+	iw.downIndent();
+	iw.println("}");
+    }
+
+    protected void writeConstrainedPropertyEventSourceMethods() throws IOException
+    {
+	iw.println("public void addVetoableChangeListener( VetoableChangeListener vcl )");
+	iw.println("{ vcs.addVetoableChangeListener( vcl ); }");
+	iw.println();
+	
+	iw.println("public void removeVetoableChangeListener( VetoableChangeListener vcl )");
+	iw.println("{ vcs.removeVetoableChangeListener( vcl ); }");
+	iw.println();
+
+	if (java_version >= 140)
+	    {
+		iw.println("public VetoableChangeListener[] getVetoableChangeListeners()");
+		iw.println("{ return vcs.getPropertyChangeListeners(); }");
+	    }
+    }
+
+    protected void writeBoundPropertyEventSourceMethods() throws IOException
+    {
+	iw.println("public void addPropertyChangeListener( PropertyChangeListener pcl )");
+	iw.println("{ pcs.addPropertyChangeListener( pcl ); }");
+	iw.println();
+	
+	iw.println("public void addPropertyChangeListener( String propName, PropertyChangeListener pcl )");
+	iw.println("{ pcs.addPropertyChangeListener( propName, pcl ); }");
+	iw.println();
+	
+	iw.println("public void removePropertyChangeListener( PropertyChangeListener pcl )");
+	iw.println("{ pcs.removePropertyChangeListener( pcl ); }");
+	iw.println();
+
+	iw.println("public void removePropertyChangeListener( String propName, PropertyChangeListener pcl )");
+	iw.println("{ pcs.removePropertyChangeListener( propName, pcl ); }");
+	iw.println();
+
+	if (java_version >= 140)
+	    {
+		iw.println("public PropertyChangeListener[] getPropertyChangeListeners()");
+		iw.println("{ return pcs.getPropertyChangeListeners(); }");
+	    }
+    }
+
+    protected void writeJavaBeansChangeSupport() throws IOException
+    {
+	if ( boundProperties() )
+	    {
+		iw.println("protected PropertyChangeSupport pcs = new PropertyChangeSupport( this );");
+		iw.println();
+		iw.println("protected PropertyChangeSupport getPropertyChangeSupport()");
+		iw.println("{ return pcs; }");
+		
+	    }
+	if ( constrainedProperties() )
+	    {
+		iw.println("protected VetoableChangeSupport vcs = new VetoableChangeSupport( this );");
+		iw.println();
+		iw.println("protected VetoableChangeSupport getVetoableChangeSupport()");
+		iw.println("{ return vcs; }");
+	    }
+    }
+
+    protected void writeOtherVariables() throws IOException //hook method for subclasses
+    {}
+
+    protected void writeOtherFunctions() throws IOException //hook method for subclasses
+    {}
+
+    protected void writeOtherClasses() throws IOException //hook method for subclasses
+    {}
+
+    protected void writePropertyVariables() throws IOException
+    {
+	for (int i = 0, len = props.length; i < len; ++i)
+	    writePropertyVariable( props[i] );
+    }
+
+    protected void writePropertyVariable( Property prop ) throws IOException
+    {
+	BeangenUtils.writePropertyVariable( prop, iw );
+// 	iw.print( CodegenUtils.getModifierString( prop.getVariableModifiers() ) );
+// 	iw.print( ' ' + prop.getSimpleTypeName() + ' ' + prop.getName());
+// 	String dflt = prop.getDefaultValueExpression();
+// 	if (dflt != null)
+// 	    iw.print( " = " + dflt );
+// 	iw.println(';');
+    }
+
+    /**
+     * @deprecated
+     */
+    protected void writePropertyMembers() throws IOException 
+    { throw new InternalError("writePropertyMembers() deprecated and removed. please us writePropertyVariables()."); }
+
+    /**
+     * @deprecated
+     */
+    protected void writePropertyMember( Property prop ) throws IOException
+    { throw new InternalError("writePropertyMember() deprecated and removed. please us writePropertyVariable()."); }
+
+    protected void writeGetterSetterPairs() throws IOException
+    {
+	for (int i = 0, len = props.length; i < len; ++i)
+	    {
+		writeGetterSetterPair( props[i], propertyTypes[i] );
+		if ( i != len - 1) iw.println();
+	    }
+    }
+
+    protected void writeGetterSetterPair( Property prop, Class propType ) throws IOException
+    {
+	writePropertyGetter( prop, propType );
+	
+	if (! prop.isReadOnly() && ! force_unmodifiable)
+	    {
+		iw.println();
+		writePropertySetter( prop, propType );
+	    }
+    }
+
+    protected void writePropertyGetter( Property prop, Class propType ) throws IOException
+    { 
+	BeangenUtils.writePropertyGetter( prop, this.getGetterDefensiveCopyExpression( prop, propType ), iw );
+
+// 	String pfx = ("boolean".equals( prop.getSimpleTypeName() ) ? "is" : "get" );
+// 	iw.print( CodegenUtils.getModifierString( prop.getGetterModifiers() ) );
+// 	iw.println(' ' + prop.getSimpleTypeName() + ' ' + pfx + BeangenUtils.capitalize( prop.getName() ) + "()");
+// 	String retVal = getGetterDefensiveCopyExpression( prop, propType );
+// 	if (retVal == null) retVal = prop.getName();
+// 	iw.println("{ return " + retVal + "; }");
+    }
+
+
+//     boolean changeMarked( Property prop )
+//     { return prop.isBound() || prop.isConstrained(); }
+
+    protected void writePropertySetter( Property prop, Class propType ) throws IOException
+    {
+	BeangenUtils.writePropertySetter( prop, this.getSetterDefensiveCopyExpression( prop, propType ), iw );
+
+// 	iw.print( CodegenUtils.getModifierString( prop.getSetterModifiers() ) );
+// 	iw.print(" void set" + BeangenUtils.capitalize( prop.getName() ) + "( " + prop.getSimpleTypeName() + ' ' + prop.getName() + " )");
+// 	if ( prop.isConstrained() )
+// 	    iw.println(" throws PropertyVetoException");
+// 	else
+// 	    iw.println();
+// 	String setVal = getSetterDefensiveCopyExpression( prop, propType );
+// 	if (setVal == null) setVal = prop.getName();
+// 	iw.println('{');
+// 	iw.upIndent();
+
+
+// 	if ( changeMarked( prop ) )
+// 	    {
+// 		iw.println( prop.getSimpleTypeName() + " oldVal = this." + prop.getName() + ';');
+
+// 		String oldValExpr = "oldVal";
+// 		String newValExpr = prop.getName();
+// 		String changeCheck;
+// 		if ( propType != null && propType.isPrimitive() ) //sometimes the type can't be resolved. if so, it ain't primitive.
+// 		    {
+// 			// PropertyChange support already has overrides
+// 			// for boolean and int 
+// 			if (propType == byte.class)
+// 			    {
+// 				oldValExpr  = "new Byte( "+ oldValExpr +" )";
+// 				newValExpr  = "new Byte( "+ newValExpr +" )";
+// 			    }
+// 			else if (propType == char.class)
+// 			    {
+// 				oldValExpr  = "new Character( "+ oldValExpr +" )";
+// 				newValExpr  = "new Character( "+ newValExpr +" )";
+// 			    }
+// 			else if (propType == short.class)
+// 			    {
+// 				oldValExpr  = "new Short( "+ oldValExpr +" )";
+// 				newValExpr  = "new Short( "+ newValExpr +" )";
+// 			    }
+// 			else if (propType == float.class)
+// 			    {
+// 				oldValExpr  = "new Float( "+ oldValExpr +" )";
+// 				newValExpr  = "new Float( "+ newValExpr +" )";
+// 			    }
+// 			else if (propType == double.class)
+// 			    {
+// 				oldValExpr  = "new Double( "+ oldValExpr +" )";
+// 				newValExpr  = "new Double( "+ newValExpr +" )";
+// 			    }
+
+// 			changeCheck = "oldVal != " + prop.getName();
+// 		    }
+// 		else
+// 		    changeCheck = "! eqOrBothNull( oldVal, " + prop.getName() + " )";
+			
+// 		if ( prop.isConstrained() )
+// 		    {
+// 			iw.println("if ( " + changeCheck + " )");
+// 			iw.upIndent();
+// 			iw.println("vcs.fireVetoableChange( \"" + prop.getName() + "\", " + oldValExpr + ", " + newValExpr + " );");
+// 			iw.downIndent();
+// 		    }
+
+// 		iw.println("this." + prop.getName() + " = " + setVal + ';');
+				
+// 		if ( prop.isBound() )
+// 		    {
+// 			iw.println("if ( " + changeCheck + " )");
+// 			iw.upIndent();
+// 			iw.println("pcs.firePropertyChange( \"" + prop.getName() + "\", " + oldValExpr + ", " + newValExpr + " );");
+// 			iw.downIndent();
+// 		    }
+// 	    }
+// 	else
+// 	    	iw.println("this." + prop.getName() + " = " + setVal + ';');
+
+// 	iw.downIndent();
+// 	iw.println('}');
+    }
+
+    protected String getGetterDefensiveCopyExpression( Property prop, Class propType )
+    { return prop.getDefensiveCopyExpression(); }
+    
+    protected String getSetterDefensiveCopyExpression( Property prop, Class propType )
+    { return prop.getDefensiveCopyExpression(); }
+    
+    protected String getConstructorDefensiveCopyExpression( Property prop, Class propType )
+    { return prop.getDefensiveCopyExpression(); }
+
+    protected void writeHeader() throws IOException
+    {
+	writeBannerComments();
+	iw.println();
+	iw.println("package " + info.getPackageName() + ';');
+	iw.println();
+	writeImports();
+    }
+
+    protected void writeBannerComments() throws IOException
+    {
+	iw.println("/*");
+	iw.println(" * This class autogenerated by " + generatorName + '.');
+	iw.println(" * DO NOT HAND EDIT!");
+	iw.println(" */");
+    }
+
+    protected void writeImports() throws IOException
+    {
+	for ( Iterator ii = generalImports.iterator(); ii.hasNext(); )
+	    iw.println("import " + ii.next() + ".*;");
+	for ( Iterator ii = specificImports.iterator(); ii.hasNext(); )
+	    iw.println("import " + ii.next() + ";");
+    }
+
+    protected void writeClassDeclaration() throws IOException
+    {
+	iw.print( CodegenUtils.getModifierString( info.getModifiers() ) + " class " + info.getClassName() );
+	String superclassName = info.getSuperclassName();
+	if (superclassName != null)
+	    iw.print( " extends " + superclassName );
+	if (interfaceNames.size() > 0)
+	    {
+		iw.print(" implements ");
+		boolean first = true;
+		for (Iterator ii = interfaceNames.iterator(); ii.hasNext(); )
+		    {
+			if (first) 
+			    first = false;
+			else
+			    iw.print(", ");
+				
+			iw.print( (String) ii.next() );
+		    }
+	    }
+	iw.println();
+    }
+
+    boolean boundProperties()
+    { return BeangenUtils.hasBoundProperties( props ); }
+
+    boolean constrainedProperties()
+    { return BeangenUtils.hasConstrainedProperties( props ); }
+
+    public static void main( String[] argv )
+    {
+	try
+	    {
+		ClassInfo info = new SimpleClassInfo("test",
+						     Modifier.PUBLIC,
+						     argv[0],
+						     null,
+						     null,
+						     new String[] {"java.awt"},
+						     null);
+		
+		Property[] props = 
+		    {
+			new SimpleProperty( "number",
+					    "int",
+					    null,
+					    "7",
+					    false,
+					    true,
+					    false
+					    ),
+			new SimpleProperty( "fpNumber",
+					    "float",
+					    null,
+					    null,
+					    true,
+					    true,
+					    false
+					    ),
+			new SimpleProperty( "location",
+					    "Point",
+					    "new Point( location.x, location.y )",
+					    "new Point( 0, 0 )",
+					    false,
+					    true,
+					    true
+					    )
+		    };
+
+		FileWriter fw = new FileWriter( argv[0] + ".java" );
+		SimplePropertyBeanGenerator g = new SimplePropertyBeanGenerator();
+		g.addExtension( new SerializableExtension() );
+		g.generate(info, props, fw );
+		fw.flush();
+		fw.close();
+	    }
+	catch ( Exception e )
+	    { e.printStackTrace(); }
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/SimplePropertyMask.java b/src/classes/com/mchange/v2/codegen/bean/SimplePropertyMask.java
new file mode 100644
index 0000000..0626c8c
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/SimplePropertyMask.java
@@ -0,0 +1,64 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.lang.reflect.Modifier;
+
+class SimplePropertyMask implements Property
+{
+    Property p;
+
+    SimplePropertyMask(Property p)
+    { this.p = p; }
+
+    public int getVariableModifiers()
+    { return Modifier.PRIVATE; }
+
+    public String  getName()
+    { return p.getName(); }
+
+    public String  getSimpleTypeName()
+    { return p.getSimpleTypeName(); }
+
+    public String getDefensiveCopyExpression()
+    { return null; }
+
+    public String getDefaultValueExpression()
+    { return p.getDefaultValueExpression(); }
+
+    public int getGetterModifiers()
+    { return Modifier.PUBLIC; }
+
+    public int getSetterModifiers()
+    { return Modifier.PUBLIC; }
+
+    public boolean isReadOnly()
+    { return false; }
+
+    public boolean isBound()
+    { return false; }
+
+    public boolean isConstrained()
+    { return false; }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/SimpleStateBeanImportExportGeneratorExtension.java b/src/classes/com/mchange/v2/codegen/bean/SimpleStateBeanImportExportGeneratorExtension.java
new file mode 100644
index 0000000..ae1ccbb
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/SimpleStateBeanImportExportGeneratorExtension.java
@@ -0,0 +1,108 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.util.*;
+import java.lang.reflect.Modifier;
+import java.io.IOException;
+import com.mchange.v2.codegen.CodegenUtils;
+import com.mchange.v2.codegen.IndentedWriter;
+
+public class SimpleStateBeanImportExportGeneratorExtension implements GeneratorExtension 
+{
+    int ctor_modifiers = Modifier.PUBLIC;
+
+    public Collection extraGeneralImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraSpecificImports()
+    { return Collections.EMPTY_SET; }
+
+    public Collection extraInterfaceNames()
+    { return Collections.EMPTY_SET; }
+
+    static class SimplePropertyMask implements Property
+    {
+	Property p;
+
+	SimplePropertyMask(Property p)
+	{ this.p = p; }
+
+	public int getVariableModifiers()
+	{ return Modifier.PRIVATE; }
+
+	public String  getName()
+	{ return p.getName(); }
+
+	public String  getSimpleTypeName()
+	{ return p.getSimpleTypeName(); }
+
+	public String getDefensiveCopyExpression()
+	{ return null; }
+
+	public String getDefaultValueExpression()
+	{ return null; }
+
+	public int getGetterModifiers()
+	{ return Modifier.PUBLIC; }
+
+	public int getSetterModifiers()
+	{ return Modifier.PUBLIC; }
+
+	public boolean isReadOnly()
+	{ return false; }
+
+	public boolean isBound()
+	{ return false; }
+
+	public boolean isConstrained()
+	{ return false; }
+    }
+
+    public void generate(ClassInfo info, Class superclassType, Property[] props, Class[] propTypes, IndentedWriter iw)
+	throws IOException
+    {
+	int num_props = props.length;
+	Property[] masked = new Property[ num_props ];
+	for (int i = 0; i < num_props; ++i)
+	    masked[i] = new SimplePropertyMask( props[i] );
+
+	iw.println("protected static class SimpleStateBean implements ExportedState");
+	iw.println("{");
+	iw.upIndent();
+
+	for (int i = 0; i < num_props; ++i)
+	    {
+		masked[i] = new SimplePropertyMask( props[i] );
+		BeangenUtils.writePropertyMember( masked[i], iw );
+		iw.println();
+		BeangenUtils.writePropertyGetter( masked[i], iw );
+		iw.println();
+		BeangenUtils.writePropertySetter( masked[i], iw );
+	    }
+
+	iw.downIndent();
+	iw.println("}");
+    }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/WrapperClassInfo.java b/src/classes/com/mchange/v2/codegen/bean/WrapperClassInfo.java
new file mode 100644
index 0000000..35caa40
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/WrapperClassInfo.java
@@ -0,0 +1,40 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+public abstract class WrapperClassInfo implements ClassInfo
+{
+    ClassInfo inner;
+
+    public WrapperClassInfo(ClassInfo info)
+    { this.inner = info; }
+
+    public String   getPackageName() { return inner.getPackageName(); }
+    public int      getModifiers() { return inner.getModifiers(); }
+    public String   getClassName() { return inner.getClassName(); }
+    public String   getSuperclassName() { return inner.getSuperclassName(); }
+    public String[] getInterfaceNames() { return inner.getInterfaceNames(); }
+    public String[] getGeneralImports() { return inner.getGeneralImports(); }
+    public String[] getSpecificImports() { return inner.getSpecificImports(); }
+}
diff --git a/src/classes/com/mchange/v2/codegen/bean/WrapperProperty.java b/src/classes/com/mchange/v2/codegen/bean/WrapperProperty.java
new file mode 100644
index 0000000..d752487
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/bean/WrapperProperty.java
@@ -0,0 +1,67 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.bean;
+
+import java.lang.reflect.Modifier;
+
+public abstract class WrapperProperty implements Property
+{
+    Property p;
+
+    public WrapperProperty(Property p)
+    { this.p = p; }
+
+    protected Property getInner()
+    { return p; }
+
+    public int getVariableModifiers()
+    { return p.getVariableModifiers(); }
+
+    public String  getName()
+    { return p.getName(); }
+
+    public String  getSimpleTypeName()
+    { return p.getSimpleTypeName(); }
+
+    public String getDefensiveCopyExpression()
+    { return p.getDefensiveCopyExpression(); }
+
+    public String getDefaultValueExpression()
+    { return p.getDefaultValueExpression(); }
+
+    public int getGetterModifiers()
+    { return p.getGetterModifiers(); }
+
+    public int getSetterModifiers()
+    { return p.getSetterModifiers(); }
+
+    public boolean isReadOnly()
+    { return p.isReadOnly(); }
+
+    public boolean isBound()
+    { return p.isBound(); }
+
+    public boolean isConstrained()
+    { return p.isConstrained(); }
+}
diff --git a/src/classes/com/mchange/v2/codegen/intfc/DelegatorGenerator.java b/src/classes/com/mchange/v2/codegen/intfc/DelegatorGenerator.java
new file mode 100644
index 0000000..8d1ed00
--- /dev/null
+++ b/src/classes/com/mchange/v2/codegen/intfc/DelegatorGenerator.java
@@ -0,0 +1,259 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.codegen.intfc;
+
+import java.io.*;
+import java.util.*;
+import java.lang.reflect.*;
+import com.mchange.v2.codegen.*;
+import com.mchange.v1.lang.ClassUtils;
+
+public class DelegatorGenerator
+{
+    int class_modifiers         = Modifier.PUBLIC | Modifier.ABSTRACT;
+    int method_modifiers        = Modifier.PUBLIC;
+    int wrapping_ctor_modifiers = Modifier.PUBLIC;
+    int default_ctor_modifiers  = Modifier.PUBLIC;
+    boolean wrapping_constructor = true;
+    boolean default_constructor  = true;
+    boolean inner_getter         = true;
+    boolean inner_setter         = true;
+
+    Class   superclass           = null;
+    Class[] extraInterfaces      = null;
+
+    final static Comparator classComp = new Comparator()
+    {
+       public int compare(Object a, Object b)
+       { return ((Class) a).getName().compareTo(((Class) b).getName()); }
+    };
+
+    public void setGenerateInnerSetter( boolean b )
+    { this.inner_setter = b; }
+
+    public boolean isGenerateInnerSetter()
+    { return inner_setter; }
+
+    public void setGenerateInnerGetter( boolean b )
+    { this.inner_getter = b; }
+
+    public boolean isGenerateInnerGetter()
+    { return inner_getter; }
+
+    public void setGenerateNoArgConstructor( boolean b )
+    { this.default_constructor = b; }
+
+    public boolean isGenerateNoArgConstructor()
+    { return default_constructor; }
+
+    public void setGenerateWrappingConstructor( boolean b )
+    { this.wrapping_constructor = b; }
+
+    public boolean isGenerateWrappingConstructor()
+    { return wrapping_constructor; }
+
+    public void setWrappingConstructorModifiers( int modifiers )
+    { this.wrapping_ctor_modifiers = modifiers; }
+
+    public int getWrappingConstructorModifiers()
+    { return wrapping_ctor_modifiers; }
+
+    public void setNoArgConstructorModifiers( int modifiers )
+    { this.default_ctor_modifiers = modifiers; }
+
+    public int getNoArgConstructorModifiers()
+    { return default_ctor_modifiers; }
+
+    public void setMethodModifiers( int modifiers )
+    { this.method_modifiers = modifiers; }
+
+    public int getMethodModifiers()
+    { return method_modifiers; }
+
+    public void setClassModifiers( int modifiers )
+    { this.class_modifiers = modifiers; }
+
+    public int getClassModifiers()
+    { return class_modifiers; }
+
+    public void setSuperclass( Class superclass )
+    { this.superclass = superclass; }
+
+    public Class getSuperclass()
+    { return superclass; }
+
+    public void setExtraInterfaces( Class[] extraInterfaces )
+    { this.extraInterfaces = extraInterfaces; }
+
+    public Class[] getExtraInterfaces()
+    { return extraInterfaces; }
+
+    public void writeDelegator(Class intfcl, String genclass, Writer w) throws IOException
+    {
+	IndentedWriter iw = CodegenUtils.toIndentedWriter(w);
+	
+	String   pkg      = genclass.substring(0, genclass.lastIndexOf('.'));
+	String   sgc      = CodegenUtils.fqcnLastElement( genclass );
+	String   scn      = (superclass != null ? ClassUtils.simpleClassName( superclass ) : null);
+	String   sin      = ClassUtils.simpleClassName( intfcl );
+	String[] eins     = null;
+	if (extraInterfaces != null)
+	    {
+		eins = new String[ extraInterfaces.length ];
+		for (int i = 0, len = extraInterfaces.length; i < len; ++i)
+		    eins[i] = ClassUtils.simpleClassName( extraInterfaces[i] );
+	    }
+
+	Set    imports  = new TreeSet( classComp );
+	
+	Method[] methods = intfcl.getMethods();
+	
+	//TODO: don't add array classes!
+	//build import set
+	if (! CodegenUtils.inSamePackage( intfcl.getName(), genclass ) )
+	    imports.add( intfcl );
+	if (superclass != null && ! CodegenUtils.inSamePackage( superclass.getName(), genclass ) )
+	    imports.add( superclass );
+	if (extraInterfaces != null)
+	    {
+		for (int i = 0, len = extraInterfaces.length; i < len; ++i)
+		    {
+			Class checkMe = extraInterfaces[i];
+			if (! CodegenUtils.inSamePackage( checkMe.getName(), genclass ) )
+			    imports.add( checkMe );
+		    }
+	    }
+	for (int i = 0, len = methods.length; i < len; ++i)
+	    {
+		Class[] args = methods[i].getParameterTypes();
+		for (int j = 0, jlen = args.length; j < jlen; ++j)
+		    {
+			if (! CodegenUtils.inSamePackage( args[j].getName(), genclass ) )
+			    imports.add( CodegenUtils.unarrayClass( args[j] ) );
+		    }       
+		Class[] excClasses = methods[i].getExceptionTypes();
+		for (int j = 0, jlen = excClasses.length; j < jlen; ++j)
+		    {
+			if (! CodegenUtils.inSamePackage( excClasses[j].getName(), genclass ) )
+			    {
+				//System.err.println("Adding exception type: " + excClasses[j]);
+				imports.add( CodegenUtils.unarrayClass( excClasses[j] ) );
+			    }
+		    }       
+		if (! CodegenUtils.inSamePackage( methods[i].getReturnType().getName(), genclass ) )
+		    imports.add( CodegenUtils.unarrayClass( methods[i].getReturnType() ) );
+	    }
+	generateBannerComment( iw );
+	iw.println("package " + pkg + ';');
+	iw.println();
+	for (Iterator ii = imports.iterator(); ii.hasNext(); )
+	    iw.println("import "+ ((Class) ii.next()).getName() + ';');
+	generateExtraImports( iw );
+	iw.println();
+	iw.print(CodegenUtils.getModifierString( class_modifiers ) + " class " + sgc);
+	if (superclass != null)
+	    iw.print(" extends " + scn);
+	iw.print(" implements " + sin);
+	if (eins != null)
+	    for (int i = 0, len = eins.length; i < len; ++i)
+		iw.print(", " + eins[i]);
+	iw.println();
+	iw.println("{");
+	iw.upIndent();
+
+	iw.println("protected " + sin + " inner;");
+	iw.println();
+
+	if ( wrapping_constructor )
+	    {
+		iw.println("public" + ' ' + sgc + '(' + sin + " inner)");
+		iw.println("{ this.inner = inner; }");
+	    }
+
+	if (default_constructor)
+	    {
+		iw.println();
+		iw.println("public" + ' ' + sgc + "()");
+		iw.println("{}");
+	    }
+
+	if (inner_setter)
+	    {
+		iw.println();
+		iw.println( CodegenUtils.getModifierString( method_modifiers ) + " void setInner( " + sin + " inner )");
+		iw.println( "{ this.inner = inner; }" );
+	    }
+	if (inner_getter)
+	    {
+		iw.println();
+		iw.println( CodegenUtils.getModifierString( method_modifiers ) + ' ' + sin + " getInner()");
+		iw.println( "{ return inner; }" );
+	    }
+	iw.println();
+	for (int i = 0, len = methods.length; i < len; ++i)
+	    {
+		Method method  = methods[i];
+		Class  retType = method.getReturnType();
+
+		if (i != 0) iw.println();
+		iw.println( CodegenUtils.methodSignature( method_modifiers, method, null ) );
+		iw.println("{");
+		iw.upIndent();
+
+		generatePreDelegateCode( intfcl, genclass, method, iw );
+		generateDelegateCode( intfcl, genclass, method, iw );
+		generatePostDelegateCode( intfcl, genclass, method, iw );
+	    
+		iw.downIndent();
+		iw.println("}");
+	    }
+
+	iw.println();
+	generateExtraDeclarations( intfcl, genclass, iw );
+
+	iw.downIndent();
+    	iw.println("}");
+    }
+
+    protected void generateDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException 
+    {
+	Class  retType = method.getReturnType();
+	
+	iw.println( (retType == void.class ? "" : "return " ) + "inner." + CodegenUtils.methodCall( method ) + ";" );
+    }
+
+    protected void generateBannerComment( IndentedWriter iw ) throws IOException 
+    {
+	iw.println("/*");
+	iw.println(" * This class generated by " + this.getClass().getName());
+	iw.println(" * " + new Date());
+	iw.println(" * DO NOT HAND EDIT!!!!");
+	iw.println(" */");
+    }
+
+    protected void generateExtraImports( IndentedWriter iw ) throws IOException {}
+    protected void generatePreDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException {}
+    protected void generatePostDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException {}
+    protected void generateExtraDeclarations( Class intfcl, String genclass, IndentedWriter iw ) throws IOException {}
+}
diff --git a/src/classes/com/mchange/v2/debug/DebugConstants.java b/src/classes/com/mchange/v2/debug/DebugConstants.java
new file mode 100644
index 0000000..07cb5db
--- /dev/null
+++ b/src/classes/com/mchange/v2/debug/DebugConstants.java
@@ -0,0 +1,31 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.debug;
+
+public interface DebugConstants
+{
+    public final static int TRACE_NONE = 0;
+    public final static int TRACE_MED  = 5;
+    public final static int TRACE_MAX  = 10;
+}
diff --git a/src/classes/com/mchange/v2/debug/ThreadNameStackTraceRecorder.java b/src/classes/com/mchange/v2/debug/ThreadNameStackTraceRecorder.java
new file mode 100644
index 0000000..400080d
--- /dev/null
+++ b/src/classes/com/mchange/v2/debug/ThreadNameStackTraceRecorder.java
@@ -0,0 +1,135 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.debug;
+
+import java.text.*;
+import java.util.*;
+import com.mchange.lang.ThrowableUtils;
+
+public class ThreadNameStackTraceRecorder
+{
+    final static String NL = System.getProperty("line.separator", "\r\n");
+
+    Set set = new HashSet();
+
+    String dumpHeader;
+    String stackTraceHeader;
+
+    public ThreadNameStackTraceRecorder( String dumpHeader )
+    { this( dumpHeader, "Debug Stack Trace." ); }
+
+    public ThreadNameStackTraceRecorder( String dumpHeader, String stackTraceHeader )
+    {
+	this.dumpHeader = dumpHeader;
+	this.stackTraceHeader = stackTraceHeader;
+    }
+
+    public synchronized Object record()
+    { 
+	Record r = new Record( stackTraceHeader );
+	set.add( r );
+	return r;
+    }
+
+    public synchronized void remove( Object rec )
+    { set.remove( rec ); }
+
+    public synchronized int size()
+    { return set.size(); }
+
+    public synchronized String getDump()
+    { return getDump(null); }
+
+    public synchronized String getDump(String locationSpecificNote)
+    {
+	DateFormat df = new SimpleDateFormat("dd-MMMM-yyyy HH:mm:ss.SSSS");
+
+	StringBuffer sb = new StringBuffer(2047);
+	sb.append(NL);
+	sb.append("----------------------------------------------------");
+	sb.append(NL);
+	sb.append( dumpHeader );
+	sb.append(NL);
+	if (locationSpecificNote != null)
+	    {
+		sb.append( locationSpecificNote );
+		sb.append( NL );
+	    }
+	boolean first = true;
+	for (Iterator ii = set.iterator(); ii.hasNext(); )
+	    {
+		if (first) 
+		    first = false;
+		else 
+		    {
+			sb.append("---");
+			sb.append( NL );
+		    }
+
+		Record r = (Record) ii.next();
+		sb.append(df.format( new Date( r.time ) ));
+		sb.append(" --> Thread Name: ");
+		sb.append(r.threadName);
+		sb.append(NL);
+		sb.append("Stack Trace: ");
+		sb.append( ThrowableUtils.extractStackTrace( r.stackTrace ) );
+	    }
+	sb.append("----------------------------------------------------");
+	sb.append(NL);
+	return sb.toString();	
+    }
+
+    private final static class Record implements Comparable
+    {
+	long time;
+	String threadName;
+	Throwable stackTrace;
+
+	Record(String sth)
+	{
+	    this.time = System.currentTimeMillis();
+	    this.threadName = Thread.currentThread().getName();
+	    this.stackTrace = new Exception( sth );
+	}
+
+	public int compareTo( Object o )
+	{
+	    Record oo = (Record) o;
+	    if ( this.time > oo.time )
+		return 1;
+	    else if (this.time < oo.time )
+		return -1;
+	    else
+		{
+		    int mine = System.identityHashCode( this );
+		    int yours = System.identityHashCode( oo );
+		    if (mine > yours)
+			return 1;
+		    else if (mine < yours)
+			return -1;
+		    return 0;
+		}
+	}
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/encounter/AbstractEncounterCounter.java b/src/classes/com/mchange/v2/encounter/AbstractEncounterCounter.java
new file mode 100644
index 0000000..ec30817
--- /dev/null
+++ b/src/classes/com/mchange/v2/encounter/AbstractEncounterCounter.java
@@ -0,0 +1,57 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.encounter;
+
+import java.util.Map;
+
+class AbstractEncounterCounter implements EncounterCounter
+{
+    final static Long ONE = new Long(1);
+    Map m;
+
+    AbstractEncounterCounter(Map m)
+    { this.m = m; }
+
+    /**
+     *  @return how many times have I seen this object before?
+     */
+    public long encounter(Object o)
+    {
+	Long oldLong = (Long) m.get(o);
+	Long newLong;
+	long out;
+	if (oldLong == null)
+	    {
+		out = 0;
+		newLong = ONE;
+	    }
+	else
+	    {
+		out = oldLong.longValue(); 
+		newLong = new Long(out + 1);
+	    }
+	m.put( o, newLong );
+	return out;
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/encounter/EncounterCounter.java b/src/classes/com/mchange/v2/encounter/EncounterCounter.java
new file mode 100644
index 0000000..3f91e88
--- /dev/null
+++ b/src/classes/com/mchange/v2/encounter/EncounterCounter.java
@@ -0,0 +1,32 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.encounter;
+
+public interface EncounterCounter
+{
+    /**
+     *  @return how many times have I seen this object before?
+     */
+    public long encounter(Object o);
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/encounter/EqualityEncounterCounter.java b/src/classes/com/mchange/v2/encounter/EqualityEncounterCounter.java
new file mode 100644
index 0000000..91f105f
--- /dev/null
+++ b/src/classes/com/mchange/v2/encounter/EqualityEncounterCounter.java
@@ -0,0 +1,33 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.encounter;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+
+public class EqualityEncounterCounter extends AbstractEncounterCounter
+{
+    public EqualityEncounterCounter()
+    { super( new WeakHashMap() ); }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/holders/ChangeNotifyingSynchronizedIntHolder.java b/src/classes/com/mchange/v2/holders/ChangeNotifyingSynchronizedIntHolder.java
new file mode 100644
index 0000000..e98f8f4
--- /dev/null
+++ b/src/classes/com/mchange/v2/holders/ChangeNotifyingSynchronizedIntHolder.java
@@ -0,0 +1,98 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.holders;
+
+import java.io.*;
+import com.mchange.v2.ser.UnsupportedVersionException;
+
+public final class ChangeNotifyingSynchronizedIntHolder implements ThreadSafeIntHolder, Serializable
+{
+    transient int value;
+    transient boolean notify_all;
+
+    public ChangeNotifyingSynchronizedIntHolder( int value, boolean notify_all )
+    { 
+	this.value = value; 
+	this.notify_all = notify_all;
+    }
+
+    public ChangeNotifyingSynchronizedIntHolder()
+    { this(0, true); }
+
+    public synchronized int getValue()
+    { return value; }
+
+    public synchronized void setValue(int value)
+    {
+	if (value != this.value)
+	    {
+		this.value = value; 
+		doNotify();
+	    }
+    }
+
+    public synchronized void increment()
+    { 
+	++value; 
+	doNotify();
+    }
+
+    public synchronized void decrement()
+    { 
+	--value; 
+	doNotify();
+    }
+
+    //must be called from a sync'ed block...
+    private void doNotify()
+    {
+	if (notify_all) this.notifyAll();
+	else this.notify();
+    }
+
+    //Serialization
+    static final long serialVersionUID = 1; //override to take control of versioning
+    private final static short VERSION = 0x0001;
+    
+    private void writeObject(ObjectOutputStream out) throws IOException
+    {
+	out.writeShort(VERSION);
+	out.writeInt(value);
+	out.writeBoolean(notify_all);
+    }
+    
+    private void readObject(ObjectInputStream in) throws IOException
+    {
+	short version = in.readShort();
+	switch (version)
+	    {
+	    case 0x0001:
+		this.value = in.readInt();
+		this.notify_all = in.readBoolean();
+		break;
+	    default:
+		throw new UnsupportedVersionException(this, version);
+	    }
+    }
+}
diff --git a/src/classes/com/mchange/v2/holders/SynchronizedIntHolder.java b/src/classes/com/mchange/v2/holders/SynchronizedIntHolder.java
new file mode 100644
index 0000000..afd2967
--- /dev/null
+++ b/src/classes/com/mchange/v2/holders/SynchronizedIntHolder.java
@@ -0,0 +1,73 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.holders;
+
+import java.io.*;
+import com.mchange.v2.ser.UnsupportedVersionException;
+
+public class SynchronizedIntHolder implements ThreadSafeIntHolder, Serializable
+{
+    transient int value;
+
+    public SynchronizedIntHolder( int value )
+    { this.value = value; }
+
+    public SynchronizedIntHolder()
+    { this(0); }
+
+    public synchronized int getValue()
+    { return value; }
+
+    public synchronized void setValue(int value)
+    { this.value = value; }
+
+    public synchronized void increment()
+    { ++value; }
+
+    public synchronized void decrement()
+    { --value; }
+
+    //Serialization
+    static final long serialVersionUID = 1; //override to take control of versioning
+    private final static short VERSION = 0x0001;
+    
+    private void writeObject(ObjectOutputStream out) throws IOException
+    {
+	out.writeShort(VERSION);
+	out.writeInt(value);
+    }
+    
+    private void readObject(ObjectInputStream in) throws IOException
+    {
+	short version = in.readShort();
+	switch (version)
+	    {
+	    case 0x0001:
+		this.value = in.readInt();
+		break;
+	    default:
+		throw new UnsupportedVersionException(this, version);
+	    }
+    }
+}
diff --git a/src/classes/com/mchange/v2/holders/ThreadSafeIntHolder.java b/src/classes/com/mchange/v2/holders/ThreadSafeIntHolder.java
new file mode 100644
index 0000000..3771c75
--- /dev/null
+++ b/src/classes/com/mchange/v2/holders/ThreadSafeIntHolder.java
@@ -0,0 +1,30 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.holders;
+
+public interface ThreadSafeIntHolder
+{
+    public int  getValue();
+    public void setValue(int i);
+}
diff --git a/src/classes/com/mchange/v2/io/IndentedWriter.java b/src/classes/com/mchange/v2/io/IndentedWriter.java
new file mode 100644
index 0000000..3d1cc7c
--- /dev/null
+++ b/src/classes/com/mchange/v2/io/IndentedWriter.java
@@ -0,0 +1,154 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.io;
+
+import java.io.*;
+
+public class IndentedWriter extends FilterWriter
+{
+    final static String EOL;
+
+    static
+    {
+	String eol = System.getProperty( "line.separator" );
+	EOL = ( eol != null ? eol : "\r\n" );
+    }
+
+    int indent_level = 0;
+    boolean at_line_start = true;
+
+    public IndentedWriter( Writer out )
+    { super( out ); }
+
+    private boolean isEol( char c )
+    { return ( c == '\r' || c == '\n' ); }
+
+    public void upIndent()
+    { ++indent_level; }
+
+    public void downIndent()
+    { --indent_level; }
+
+    public void write( int c ) throws IOException
+    { 
+	out.write( c );
+	at_line_start = isEol( (char) c );
+    }
+
+    public void write( char[] chars, int off, int len ) throws IOException
+    {
+	out.write( chars, off, len );
+	at_line_start = isEol( chars[ off + len - 1] );
+    }
+
+    public void write( String s, int off, int len ) throws IOException
+    {
+	if (len > 0)
+	    {
+		out.write( s, off, len );
+		at_line_start = isEol( s.charAt( off + len - 1) );
+	    }
+    }
+
+    private void printIndent() throws IOException
+    {
+	for (int i = 0; i < indent_level; ++i)
+	    out.write( '\t' );
+    }
+
+    public void print( String s ) throws IOException
+    {
+	if ( at_line_start )
+	    printIndent();
+	out.write(s);
+	char last = s.charAt( s.length() - 1 );
+	at_line_start = isEol( last );
+    }
+
+    public void println( String s ) throws IOException
+    {
+	if ( at_line_start )
+	    printIndent();
+	out.write(s);
+	out.write( EOL );
+	at_line_start = true;
+    }
+
+    public void print( boolean x ) throws IOException
+    { print( String.valueOf(x) ); }
+
+    public void print( byte x ) throws IOException
+    { print( String.valueOf(x) ); }
+
+    public void print( char x ) throws IOException
+    { print( String.valueOf(x) ); }
+
+    public void print( short x ) throws IOException
+    { print( String.valueOf(x) ); }
+
+    public void print( int x ) throws IOException
+    { print( String.valueOf(x) ); }
+
+    public void print( long x ) throws IOException
+    { print( String.valueOf(x) ); }
+
+    public void print( float x ) throws IOException
+    { print( String.valueOf(x) ); }
+
+    public void print( double x ) throws IOException
+    { print( String.valueOf(x) ); }
+
+    public void print( Object x ) throws IOException
+    { print( String.valueOf(x) ); }
+
+    public void println( boolean x ) throws IOException
+    { println( String.valueOf(x) ); }
+
+    public void println( byte x ) throws IOException
+    { println( String.valueOf(x) ); }
+
+    public void println( char x ) throws IOException
+    { println( String.valueOf(x) ); }
+
+    public void println( short x ) throws IOException
+    { println( String.valueOf(x) ); }
+
+    public void println( int x ) throws IOException
+    { println( String.valueOf(x) ); }
+
+    public void println( long x ) throws IOException
+    { println( String.valueOf(x) ); }
+
+    public void println( float x ) throws IOException
+    { println( String.valueOf(x) ); }
+
+    public void println( double x ) throws IOException
+    { println( String.valueOf(x) ); }
+
+    public void println( Object x ) throws IOException
+    { println( String.valueOf(x) ); }
+
+    public void println() throws IOException
+    { println( "" ); }
+}
diff --git a/src/classes/com/mchange/v2/lang/Coerce.java b/src/classes/com/mchange/v2/lang/Coerce.java
new file mode 100644
index 0000000..acb592e
--- /dev/null
+++ b/src/classes/com/mchange/v2/lang/Coerce.java
@@ -0,0 +1,138 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.lang;
+
+import java.util.*;
+
+public final class Coerce
+{
+    final static Set CAN_COERCE;
+    
+    static
+    {
+	Class[] classes =
+	    {
+		byte.class,
+		boolean.class,
+		char.class,
+		short.class,
+		int.class,
+		long.class,
+		float.class,
+		double.class,
+		String.class,
+		Byte.class,
+		Boolean.class,
+		Character.class,
+		Short.class,
+		Integer.class,
+		Long.class,
+		Float.class,
+		Double.class
+	    };
+	Set tmp = new HashSet();
+	tmp.addAll( Arrays.asList( classes ) );
+	CAN_COERCE = Collections.unmodifiableSet( tmp );
+    }
+
+    public static boolean canCoerce( Class cl )
+    { return CAN_COERCE.contains( cl ); }
+
+    public static boolean canCoerce( Object o )
+    { return canCoerce( o.getClass() ); }
+
+    public static int toInt( String s )
+    { 
+	try { return Integer.parseInt( s ); }
+	catch ( NumberFormatException e )
+	    { return (int) Double.parseDouble( s ); }
+    }
+
+    public static long toLong( String s )
+    { 
+	try { return Long.parseLong( s ); }
+	catch ( NumberFormatException e )
+	    { return (long) Double.parseDouble( s ); }
+    }
+
+    public static float toFloat( String s )
+    { return Float.parseFloat( s ); }
+
+    public static double toDouble( String s )
+    { return Double.parseDouble( s ); }
+
+    public static byte toByte( String s )
+    { return (byte) toInt(s); }
+
+    public static short toShort( String s )
+    { return (short) toInt(s); }
+
+    public static boolean toBoolean( String s )
+    { return Boolean.valueOf( s ).booleanValue(); }
+
+    public static char toChar( String s )
+    {
+	s = s.trim();
+	if (s.length() == 1)
+	    return s.charAt( 0 );
+	else
+	    return (char) toInt(s);
+    }
+
+    public static Object toObject( String s, Class type )
+    {
+	if ( type == byte.class) type = Byte.class;
+	else if ( type == boolean.class) type = Boolean.class;
+	else if ( type == char.class) type = Character.class;
+	else if ( type == short.class) type = Short.class;
+	else if ( type == int.class) type = Integer.class;
+	else if ( type == long.class) type = Long.class;
+	else if ( type == float.class) type = Float.class;
+	else if ( type == double.class) type = Double.class;
+
+	if ( type == String.class )
+	    return s;
+	else if ( type == Byte.class )
+	    return new Byte( toByte( s ) );
+	else if ( type == Boolean.class )
+	    return Boolean.valueOf( s );
+	else if ( type == Character.class )
+	    return new Character( toChar( s ) );
+	else if ( type == Short.class )
+	    return new Short( toShort( s ) );
+	else if ( type == Integer.class )
+	    return new Integer( s );
+	else if ( type == Long.class )
+	    return new Long( s );
+	else if ( type == Float.class )
+	    return new Float( s );
+	else if ( type == Double.class )
+	    return new Double( s );
+	else
+	    throw new IllegalArgumentException("Cannot coerce to type: " + type.getName());
+    }
+
+    private Coerce()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/lang/ObjectUtils.java b/src/classes/com/mchange/v2/lang/ObjectUtils.java
new file mode 100644
index 0000000..282a616
--- /dev/null
+++ b/src/classes/com/mchange/v2/lang/ObjectUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.lang;
+
+public final class ObjectUtils
+{
+    public static boolean eqOrBothNull(Object a, Object b)
+    {
+	if (a == b)
+	    return true;
+	else if (a == null)
+	    return false;
+	else
+	    return a.equals(b);
+    }
+	
+    /**
+     *  Note -- if you are using Arrays.equals( ... ) or similar
+     *  and want a compatible hash method, see methods in 
+     * {@link com.mchange.v1.util.ArrayUtils#hashOrZeroArray ArrayUtils}.
+     */
+    public static int hashOrZero(Object o)
+    { return (o == null ? 0 : o.hashCode()); }
+
+    private ObjectUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/lang/ThreadGroupUtils.java b/src/classes/com/mchange/v2/lang/ThreadGroupUtils.java
new file mode 100644
index 0000000..7af494a
--- /dev/null
+++ b/src/classes/com/mchange/v2/lang/ThreadGroupUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.lang;
+
+public final class ThreadGroupUtils
+{
+    public static ThreadGroup rootThreadGroup()
+    {
+	ThreadGroup tg = Thread.currentThread().getThreadGroup(); 
+	ThreadGroup ptg = tg.getParent();
+	while (ptg != null)
+	    {
+		tg = ptg;
+		ptg = tg.getParent();
+	    }
+	return tg;
+    }
+
+    private ThreadGroupUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/lang/ThreadUtils.java b/src/classes/com/mchange/v2/lang/ThreadUtils.java
new file mode 100644
index 0000000..5448458
--- /dev/null
+++ b/src/classes/com/mchange/v2/lang/ThreadUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.lang;
+
+import com.mchange.v2.log.*;
+import java.lang.reflect.Method;
+
+public final class ThreadUtils
+{
+    private final static MLogger logger = MLog.getLogger( ThreadUtils.class );
+
+    final static Method holdsLock;
+
+    static
+    {
+	Method _holdsLock;
+	try
+	    { _holdsLock = Thread.class.getMethod("holdsLock", new Class[] { Object.class }); }
+	catch (NoSuchMethodException e)
+	    { _holdsLock = null; }
+
+	holdsLock = _holdsLock;
+    }
+
+    public static void enumerateAll( Thread[] threads )
+    { ThreadGroupUtils.rootThreadGroup().enumerate( threads ); }
+
+    /**
+     * @returns null if cannot be determined, otherwise true or false
+     */
+    public static Boolean reflectiveHoldsLock( Object o )
+    {
+	try
+	    {
+		if (holdsLock == null)
+		    return null;
+		else
+		    return (Boolean) holdsLock.invoke( null, new Object[] { o } );
+	    }
+	catch (Exception e)
+	    {
+		if ( logger.isLoggable( MLevel.FINER ) )
+		    logger.log( MLevel.FINER, "An Exception occurred while trying to call Thread.holdsLock( ... ) reflectively.", e);
+		return null;
+	    }
+    }
+
+    private ThreadUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/lang/VersionUtils.java b/src/classes/com/mchange/v2/lang/VersionUtils.java
new file mode 100644
index 0000000..ca73499
--- /dev/null
+++ b/src/classes/com/mchange/v2/lang/VersionUtils.java
@@ -0,0 +1,182 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.lang;
+
+import com.mchange.v2.log.*;
+import com.mchange.v1.util.StringTokenizerUtils;
+
+public final class VersionUtils
+{
+    private final static MLogger logger = MLog.getLogger( VersionUtils.class );
+
+    private final static int[] DFLT_VERSION_ARRAY = {1,1};
+
+    private final static int[] JDK_VERSION_ARRAY;
+    private final static int JDK_VERSION; //two digit int... 10 for 1.0, 11 for 1.1, etc.
+
+    private final static Integer NUM_BITS;
+
+    static
+    {
+	String vstr = System.getProperty( "java.version" );
+	int[] v;
+	if (vstr == null)
+	    {
+		if (logger.isLoggable( MLevel.WARNING ))
+		    logger.warning("Could not find java.version System property. Defaulting to JDK 1.1");
+		v = DFLT_VERSION_ARRAY;
+	    }
+	else
+	    { 
+		try { v = extractVersionNumberArray( vstr, "._" ); }
+		catch ( NumberFormatException e )
+		    {
+			if (logger.isLoggable( MLevel.WARNING ))
+			    logger.warning("java.version ''" + vstr + "'' could not be parsed. Defaulting to JDK 1.1.");
+			v = DFLT_VERSION_ARRAY;
+		    }
+	    }
+	int jdkv = 0;
+	if (v.length > 0)
+	    jdkv += (v[0] * 10);
+	if (v.length > 1)
+	    jdkv += (v[1]);
+
+	JDK_VERSION_ARRAY = v;
+	JDK_VERSION = jdkv;
+
+	//System.err.println( JDK_VERSION );
+
+	Integer tmpNumBits;
+	try
+	    {
+		String numBitsStr = System.getProperty("sun.arch.data.model");
+		if (numBitsStr == null)
+		    tmpNumBits = null;
+		else
+		    tmpNumBits = new Integer( numBitsStr );
+	    }
+	catch (Exception e)
+	    {
+		tmpNumBits = null;
+	    }
+
+	if (tmpNumBits == null || tmpNumBits.intValue() == 32 || tmpNumBits.intValue() == 64)
+	    NUM_BITS = tmpNumBits;
+	else
+	    {
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.warning("Determined a surprising jvmNumerOfBits: " + tmpNumBits + 
+				   ". Setting jvmNumberOfBits to unknown (null).");
+		NUM_BITS = null;
+	    }
+    }
+
+    /**
+     *  @return null if unknown, 
+     *          an Integer (as of 2006 always 32 or 64)
+     *          otherwise
+     */
+    public static Integer jvmNumberOfBits()
+    { return NUM_BITS; }
+
+    public static boolean isJavaVersion10()
+    { return (JDK_VERSION == 10); }
+
+    public static boolean isJavaVersion11()
+    { return (JDK_VERSION == 11); }
+
+    public static boolean isJavaVersion12()
+    { return (JDK_VERSION == 12); }
+
+    public static boolean isJavaVersion13()
+    { return (JDK_VERSION == 13); }
+
+    public static boolean isJavaVersion14()
+    { return (JDK_VERSION == 14); }
+
+    public static boolean isJavaVersion15()
+    { return (JDK_VERSION == 15); }
+
+    public static boolean isAtLeastJavaVersion10()
+    { return (JDK_VERSION >= 10); }
+
+    public static boolean isAtLeastJavaVersion11()
+    { return (JDK_VERSION >= 11); }
+
+    public static boolean isAtLeastJavaVersion12()
+    { return (JDK_VERSION >= 12); }
+
+    public static boolean isAtLeastJavaVersion13()
+    { return (JDK_VERSION >= 13); }
+
+    public static boolean isAtLeastJavaVersion14()
+    { return (JDK_VERSION >= 14); }
+
+    public static boolean isAtLeastJavaVersion15()
+    { return (JDK_VERSION >= 15); }
+
+    public static int[] extractVersionNumberArray(String versionString, String delims)
+	throws NumberFormatException
+    {
+	String[] intStrs = StringTokenizerUtils.tokenizeToArray( versionString, delims, false );
+	int len = intStrs.length;
+	int[] out = new int[ len ];
+	for (int i = 0; i < len; ++i)
+	    out[i] = Integer.parseInt( intStrs[i] );
+	return out;
+    }
+
+    public boolean prefixMatches( int[] pfx, int[] fullVersion )
+    {
+	if (pfx.length > fullVersion.length)
+	    return false;
+	else
+	    {
+		for (int i = 0, len = pfx.length; i < len; ++i)
+		    if (pfx[i] != fullVersion[i])
+			return false;
+		return true;
+	    }
+    }
+
+    public static int lexicalCompareVersionNumberArrays(int[] a, int[] b)
+    {
+	int alen = a.length;
+	int blen = b.length;
+	for (int i = 0; i < alen; ++i)
+	    {
+		if (i == blen)
+		    return 1; //a is larger if they are the same to a point, but a has an extra version number
+		else if (a[i] > b[i])
+		    return 1;
+		else if (a[i] < b[i])
+		    return -1;
+	    }
+	if (blen > alen)
+	    return -1; //a is smaller if they are the same to a point, but b has an extra version number
+	else
+	    return 0;
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/log/FallbackMLog.java b/src/classes/com/mchange/v2/log/FallbackMLog.java
new file mode 100644
index 0000000..12b239a
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/FallbackMLog.java
@@ -0,0 +1,357 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log;
+
+import java.text.*;
+import java.util.*;
+import java.util.logging.*;
+import com.mchange.lang.ThrowableUtils;
+
+public final class FallbackMLog extends MLog
+{
+    final static MLevel DEFAULT_CUTOFF_LEVEL;
+
+    static
+    {
+	MLevel dflt = null;
+	String dfltName = MLog.CONFIG.getProperty( "com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL" );
+	if (dfltName != null)
+	    dflt = MLevel.fromSeverity( dfltName );
+	if (dflt == null)
+	    dflt = MLevel.INFO;
+	DEFAULT_CUTOFF_LEVEL = dflt;
+    }
+
+    MLogger logger = new FallbackMLogger();
+
+    public synchronized MLogger getMLogger(String name)
+    { return logger; }
+
+    public MLogger getMLogger(Class cl)
+    { return getLogger( cl.getName() ); }
+
+
+    public MLogger getMLogger()
+    { return logger; } 
+
+    private final static class FallbackMLogger implements MLogger
+    {
+	MLevel cutoffLevel = DEFAULT_CUTOFF_LEVEL;
+
+	private void formatrb(MLevel l, String srcClass, String srcMeth, String rbname, String msg, Object[] params, Throwable t)
+	{
+	    ResourceBundle rb = ResourceBundle.getBundle( rbname );
+	    if (msg != null && rb != null)
+		{
+		    String check = rb.getString( msg );
+		    if (check != null)
+			msg = check;
+		}
+	    format( l, srcClass, srcMeth, msg, params, t);
+	}
+
+	private void format(MLevel l, String srcClass, String srcMeth, String msg, Object[] params, Throwable t)
+	{ System.err.println( formatString( l, srcClass, srcMeth, msg, params, t ) ); }
+
+	private String formatString(MLevel l, String srcClass, String srcMeth, String msg, Object[] params, Throwable t)
+	{
+	    boolean add_parens = (srcMeth != null && ! srcMeth.endsWith(")"));
+		
+	    StringBuffer sb = new StringBuffer(256);
+	    sb.append(l.getLineHeader());
+	    sb.append(' ');
+	    if (srcClass != null && srcMeth != null)
+		{
+		    sb.append('[');
+		    sb.append( srcClass );
+		    sb.append( '.' );
+		    sb.append( srcMeth );
+		    if (add_parens)
+			sb.append("()");
+		    sb.append( ']' );
+		}
+	    else if (srcClass != null)
+		{
+		    sb.append('[');
+		    sb.append( srcClass );
+		    sb.append( ']' );
+		}
+	    else if (srcMeth != null)
+		{
+		    sb.append('[');
+		    sb.append( srcMeth );
+		    if (add_parens)
+			sb.append("()");
+		    sb.append( ']' );
+		}
+	    if (msg == null) 
+		{
+		    if (params != null)
+			{
+			    sb.append("params: ");
+			    for (int i = 0, len = params.length; i < len; ++i)
+				{
+				    if (i != 0) sb.append(", ");
+				    sb.append( params[i] );
+				}
+			}
+		}
+	    else 
+		{
+		    if (params == null)
+			sb.append( msg );
+		    else
+			{
+			    MessageFormat mfmt = new MessageFormat( msg );
+			    sb.append( mfmt.format( params ) );
+			}
+		}
+	    
+	    if (t != null)
+		sb.append( ThrowableUtils.extractStackTrace( t ) );
+
+	    return sb.toString();
+	}
+
+	public ResourceBundle getResourceBundle()
+	{
+	    //warn("Using logger " + this.getClass().getName() + ", which does not support ResourceBundles.");
+	    return null;
+	}
+
+	public String getResourceBundleName()
+	{ return null; }
+
+	public void setFilter(Object java14Filter) throws SecurityException
+	{
+	    warning("Using FallbackMLog -- Filters not supported!");
+	}
+
+	public Object getFilter()
+	{ 
+	    return null; 
+	}
+
+	public void log(MLevel l, String msg)
+	{ 
+	    if ( isLoggable( l ) )
+		format( l, null, null, msg, null, null ); 
+	}
+
+	public void log(MLevel l, String msg, Object param)
+	{ 
+	    if ( isLoggable( l ) )
+		format( l, null, null, msg, new Object[] { param }, null ); 
+	}
+
+	public void log(MLevel l,String msg, Object[] params)
+	{ 
+	    if ( isLoggable( l ) )
+		format( l, null, null, msg, params, null ); 
+	}
+
+	public void log(MLevel l, String msg, Throwable t)
+	{ 
+	    if ( isLoggable( l ) )
+		format( l, null, null, msg, null, t ); 
+	}
+
+	public void logp(MLevel l, String srcClass, String srcMeth, String msg)
+	{ 
+	    if ( isLoggable( l ) )
+		format( l, srcClass, srcMeth, msg, null, null ); 
+	}
+
+	public void logp(MLevel l, String srcClass, String srcMeth, String msg, Object param)
+	{ 
+	    if ( isLoggable( l ) )
+		format( l, srcClass, srcMeth, msg, new Object[] { param }, null ); 
+	}
+
+	public void logp(MLevel l, String srcClass, String srcMeth, String msg, Object[] params)
+	{ 
+	    if ( isLoggable( l ) )
+		format( l, srcClass, srcMeth, msg, params, null ); 
+	}
+
+	public void logp(MLevel l, String srcClass, String srcMeth, String msg, Throwable t)
+	{ 
+	    if ( isLoggable( l ) )
+		format( l, srcClass, srcMeth, msg, null, t ); 
+	}
+
+	public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg)
+	{ 
+	    if ( isLoggable( l ) )
+		formatrb( l, srcClass, srcMeth, rb, msg, null, null ); 
+	}
+
+	public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object param)
+	{ 
+	    if ( isLoggable( l ) )
+		formatrb( l, srcClass, srcMeth, rb, msg, new Object[] { param }, null ); 
+	}
+
+	public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object[] params)
+	{ 
+	    if ( isLoggable( l ) )
+		formatrb( l, srcClass, srcMeth, rb, msg, params, null ); 
+	}
+
+	public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Throwable t)
+	{ 
+	    if ( isLoggable( l ) )
+		formatrb( l, srcClass, srcMeth, rb, msg, null, t ); 
+	}
+
+	public void entering(String srcClass, String srcMeth)
+	{ 
+	    if ( isLoggable( MLevel.FINER ) )
+		format(MLevel.FINER, srcClass, srcMeth, "Entering method.", null, null); 
+	}
+
+	public void entering(String srcClass, String srcMeth, Object param)
+	{ 
+	    if ( isLoggable( MLevel.FINER ) )
+		format(MLevel.FINER, srcClass, srcMeth, "Entering method with argument " + param, null, null); 
+	}
+
+	public void entering(String srcClass, String srcMeth, Object[] params)
+	{ 
+	    if ( isLoggable( MLevel.FINER ) )
+		{
+		    if (params == null)
+			entering( srcClass, srcMeth );
+		    else
+			{
+			    StringBuffer sb = new StringBuffer(128);
+			    sb.append("( ");
+			    for (int i = 0, len = params.length; i < len; ++i)
+				{
+				    if (i != 0) sb.append(", ");
+				    sb.append( params[i] );
+				}
+			    sb.append(" )");
+			    format(MLevel.FINER, srcClass, srcMeth, "Entering method with arguments " + sb.toString(), null, null); 
+			}
+		}
+	}
+
+	public void exiting(String srcClass, String srcMeth)
+	{ 
+	    if ( isLoggable( MLevel.FINER ) )
+		format(MLevel.FINER, srcClass, srcMeth, "Exiting method.", null, null); 
+	}
+
+	public void exiting(String srcClass, String srcMeth, Object result)
+	{ 
+	    if ( isLoggable( MLevel.FINER ) )
+		format(MLevel.FINER, srcClass, srcMeth, "Exiting method with result " + result, null, null); 
+	}
+
+	public void throwing(String srcClass, String srcMeth, Throwable t)
+	{ 
+	    if ( isLoggable( MLevel.FINE ) )
+		format(MLevel.FINE, srcClass, srcMeth, "Throwing exception." , null, t); 
+	}
+
+	public void severe(String msg)
+	{ 
+	    if ( isLoggable( MLevel.SEVERE ) )
+		format(MLevel.SEVERE, null, null, msg, null, null); 
+	}
+
+	public void warning(String msg)
+	{ 
+	    if ( isLoggable( MLevel.WARNING ) )
+		format(MLevel.WARNING, null, null, msg, null, null); 
+	}
+
+	public void info(String msg)
+	{ 
+	    if ( isLoggable( MLevel.INFO ) )
+		format(MLevel.INFO, null, null, msg, null, null); 
+	}
+
+	public void config(String msg)
+	{ 
+	    if ( isLoggable( MLevel.CONFIG ) )
+		format(MLevel.CONFIG, null, null, msg, null, null); 
+	}
+
+	public void fine(String msg)
+	{ 
+	    if ( isLoggable( MLevel.FINE ) )
+		format(MLevel.FINE, null, null, msg, null, null); 
+	}
+
+	public void finer(String msg)
+	{ 
+	    if ( isLoggable( MLevel.FINER ) )
+		format(MLevel.FINER, null, null, msg, null, null); 
+	}
+
+	public void finest(String msg)
+	{ 
+	    if ( isLoggable( MLevel.FINEST ) )
+		format(MLevel.FINEST, null, null, msg, null, null); 
+	}
+
+	public void setLevel(MLevel l) throws SecurityException
+	{ this.cutoffLevel = l; }
+					      
+	public synchronized MLevel getLevel()
+	{ return cutoffLevel; }
+
+	public synchronized boolean isLoggable(MLevel l)
+	{ return (l.intValue() >= cutoffLevel.intValue()); }
+
+	public String getName()
+	{ return "global"; }
+
+	public void addHandler(Object h) throws SecurityException
+	{ 
+	    warning("Using FallbackMLog -- Handlers not supported."); 
+	}
+
+	public void removeHandler(Object h) throws SecurityException
+	{
+	    warning("Using FallbackMLog -- Handlers not supported.");
+	}
+
+	public Object[] getHandlers()
+	{ 
+	    warning("Using FallbackMLog -- Handlers not supported.");
+	    return new Object[0];
+	}
+
+	public void setUseParentHandlers(boolean uph)
+	{ 
+	    warning("Using FallbackMLog -- Handlers not supported.");
+	}
+
+	public boolean getUseParentHandlers()
+	{ return false;	}
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/log/LogUtils.java b/src/classes/com/mchange/v2/log/LogUtils.java
new file mode 100644
index 0000000..83e6963
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/LogUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log;
+
+public final class LogUtils
+{
+    public static String createParamsList(Object[] params)
+    {
+	StringBuffer sb = new StringBuffer(511);
+	LogUtils.appendParamsList( sb, params );
+	return sb.toString();
+    }
+
+    public static void appendParamsList(StringBuffer sb, Object[] params)
+    {
+	sb.append("[params: ");
+	for (int i = 0, len = params.length; i < len; ++i)
+	    {
+		if (i != 0) sb.append(", ");
+		sb.append( params[i] );
+	    }
+	sb.append(']');
+    }
+    
+    private LogUtils()
+    {}
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/log/MLevel.java b/src/classes/com/mchange/v2/log/MLevel.java
new file mode 100644
index 0000000..6cea61f
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/MLevel.java
@@ -0,0 +1,155 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log;
+
+import java.util.*;
+
+public final class MLevel
+{
+    public final static MLevel ALL;
+    public final static MLevel CONFIG;
+    public final static MLevel FINE;
+    public final static MLevel FINER;
+    public final static MLevel FINEST;
+    public final static MLevel INFO;
+    public final static MLevel OFF;
+    public final static MLevel SEVERE;
+    public final static MLevel WARNING;
+
+    private final static Map integersToMLevels;
+    private final static Map namesToMLevels;
+
+    public static MLevel fromIntValue(int intval)
+    { return (MLevel) integersToMLevels.get( new Integer( intval ) ); }
+
+    public static MLevel fromSeverity(String name)
+    { return (MLevel) namesToMLevels.get( name ); }
+
+    static
+    {
+	Class lvlClass;
+	boolean jdk14api;  //not just jdk14 -- it is possible for the api to be present with older vms
+	try
+	    { 
+		lvlClass = Class.forName( "java.util.logging.Level" ); 
+		jdk14api = true;
+	    }
+	catch (ClassNotFoundException e )
+	    { 
+		lvlClass = null;
+		jdk14api = false; 
+	    }
+
+	MLevel all;
+	MLevel config;
+	MLevel fine;
+	MLevel finer;
+	MLevel finest;
+	MLevel info;
+	MLevel off;
+	MLevel severe;
+	MLevel warning;
+
+	try
+	    { 
+		// numeric values match the intvalues from java.util.logging.Level
+		all = new MLevel( (jdk14api ? lvlClass.getField("ALL").get(null) : null), Integer.MIN_VALUE, "ALL" );
+		config = new MLevel( (jdk14api ? lvlClass.getField("CONFIG").get(null) : null), 700, "CONFIG" );
+		fine = new MLevel( (jdk14api ? lvlClass.getField("FINE").get(null) : null), 500, "FINE" );
+		finer = new MLevel( (jdk14api ? lvlClass.getField("FINER").get(null) : null), 400, "FINER" );
+		finest = new MLevel( (jdk14api ? lvlClass.getField("FINEST").get(null) : null), 300, "FINEST" );
+		info = new MLevel( (jdk14api ? lvlClass.getField("INFO").get(null) : null), 800, "INFO" );
+		off = new MLevel( (jdk14api ? lvlClass.getField("OFF").get(null) : null), Integer.MAX_VALUE, "OFF" );
+		severe = new MLevel( (jdk14api ? lvlClass.getField("SEVERE").get(null) : null), 900, "SEVERE" );
+		warning = new MLevel( (jdk14api ? lvlClass.getField("WARNING").get(null) : null), 1000, "WARNING" );
+	    }
+	catch ( Exception e )
+	    { 
+		e.printStackTrace();
+		throw new InternalError("Huh? java.util.logging.Level is here, but not its expected public fields?");
+	    }
+
+	ALL = all;
+	CONFIG = config;
+	FINE = fine;
+	FINER = finer;
+	FINEST = finest;
+	INFO = info;
+	OFF = off;
+	SEVERE = severe;
+	WARNING = warning;
+
+	Map tmp = new HashMap();
+	tmp.put( new Integer(all.intValue()), all);
+	tmp.put( new Integer(config.intValue()), config);
+	tmp.put( new Integer(fine.intValue()), fine);
+	tmp.put( new Integer(finer.intValue()), finer);
+	tmp.put( new Integer(finest.intValue()), finest);
+	tmp.put( new Integer(info.intValue()), info);
+	tmp.put( new Integer(off.intValue()), off);
+	tmp.put( new Integer(severe.intValue()), severe);
+	tmp.put( new Integer(warning.intValue()), warning);
+
+	integersToMLevels = Collections.unmodifiableMap( tmp );
+
+	tmp = new HashMap();
+	tmp.put( all.getSeverity(), all);
+	tmp.put( config.getSeverity(), config);
+	tmp.put( fine.getSeverity(), fine);
+	tmp.put( finer.getSeverity(), finer);
+	tmp.put( finest.getSeverity(), finest);
+	tmp.put( info.getSeverity(), info);
+	tmp.put( off.getSeverity(), off);
+	tmp.put( severe.getSeverity(), severe);
+	tmp.put( warning.getSeverity(), warning);
+
+	namesToMLevels = Collections.unmodifiableMap( tmp );
+    }
+
+    Object level;
+    int    intval;
+    String lvlstring;
+
+    public int intValue()
+    { return intval; }
+
+    public Object asJdk14Level()
+    { return level; }
+
+    public String getSeverity()
+    { return lvlstring; }
+
+    public String toString()
+    { return this.getClass().getName() + this.getLineHeader(); }
+
+    public String getLineHeader()
+    { return "[" + lvlstring + ']';}
+
+    private MLevel(Object level, int intval, String lvlstring)
+    {
+	this.level = level;
+	this.intval = intval;
+	this.lvlstring = lvlstring;
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/log/MLog.java b/src/classes/com/mchange/v2/log/MLog.java
new file mode 100644
index 0000000..c5e75f0
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/MLog.java
@@ -0,0 +1,251 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log;
+
+import java.util.List;
+import java.util.ArrayList;
+import com.mchange.v1.util.StringTokenizerUtils;
+import com.mchange.v2.cfg.MultiPropertiesConfig;
+
+public abstract class MLog
+{
+    final static NameTransformer transformer;
+    final static MLog mlog;
+
+    final static MultiPropertiesConfig CONFIG;
+
+    final static MLogger logger;
+
+    static
+    {
+	String[] defaults = new String[]
+	{
+	    "/com/mchange/v2/log/default-mchange-log.properties",
+	    "/mchange-log.properties",
+	    "/"
+	};
+	CONFIG = MultiPropertiesConfig.readVmConfig( defaults, null );
+
+	String classnamesStr = CONFIG.getProperty("com.mchange.v2.log.MLog");
+	String[] classnames = null;
+	if (classnamesStr == null)
+	    classnamesStr = CONFIG.getProperty("com.mchange.v2.log.mlog");
+	if (classnamesStr != null)
+	    classnames = StringTokenizerUtils.tokenizeToArray( classnamesStr, ", \t\r\n" );
+
+	boolean warn = false;
+	MLog tmpml = null;
+	if (classnames != null)
+	    tmpml = findByClassnames( classnames );
+	if (tmpml == null)
+	    tmpml = findByClassnames( MLogClasses.CLASSNAMES );
+	if (tmpml == null)
+	    {
+		warn = true;
+		tmpml = new FallbackMLog();
+	    }
+	mlog = tmpml;
+	if (warn)
+	    info("Using " + mlog.getClass().getName() + " -- Named logger's not supported, everything goes to System.err.");
+
+	logger = mlog.getLogger( MLog.class );
+	String loggerDesc = mlog.getClass().getName();
+	if ("com.mchange.v2.log.jdk14logging.Jdk14MLog".equals( loggerDesc ))
+	    loggerDesc = "java 1.4+ standard";
+	else if ("com.mchange.v2.log.log4j.Log4jMLog".equals( loggerDesc ))
+	    loggerDesc = "log4j";
+	
+	if (logger.isLoggable( MLevel.INFO ))
+	    logger.log( MLevel.INFO, "MLog clients using " + loggerDesc + " logging.");
+
+	NameTransformer tmpt = null;
+	String tClassName = CONFIG.getProperty("com.mchange.v2.log.NameTransformer");
+	if (tClassName == null)
+	    tClassName = CONFIG.getProperty("com.mchange.v2.log.nametransformer");
+	try
+	    { 
+		if (tClassName != null)
+		    tmpt = (NameTransformer) Class.forName( tClassName ).newInstance();
+	    }
+	catch ( Exception e )
+	    {
+		System.err.println("Failed to instantiate com.mchange.v2.log.NameTransformer '" + tClassName + "'!"); 
+		e.printStackTrace();
+	    }
+	transformer = tmpt;
+
+	//System.err.println(mlog);
+    }
+
+    public static MLog findByClassnames( String[] classnames )
+    {
+	List attempts = null;
+	for (int i = 0, len = classnames.length; i < len; ++i)
+	    {
+		try { return (MLog) Class.forName( classnames[i] ).newInstance(); }
+		catch (Exception e)
+		    { 
+			if (attempts == null)
+			    attempts = new ArrayList();
+			attempts.add( classnames[i] );
+// 			System.err.println("com.mchange.v2.log.MLog '" + classnames[i] + "' could not be loaded!"); 
+// 			e.printStackTrace();
+		    }
+	    }
+	System.err.println("Tried without success to load the following MLog classes:");
+	for (int i = 0, len = attempts.size(); i < len; ++i)
+	    System.err.println("\t" + attempts.get(i));
+	return null;
+    }
+
+    public static MLog instance()
+    { return mlog; }
+
+    public static MLogger getLogger(String name) 
+    {
+	MLogger out;
+	if ( transformer == null )
+	    out = instance().getMLogger( name );
+	else
+	    {
+		String xname = transformer.transformName( name );
+		if (xname != null)
+		    out = instance().getMLogger( xname );
+		else
+		    out = instance().getMLogger( name );
+	    }
+	return out;
+    }
+
+    public static MLogger getLogger(Class cl)
+    {
+	MLogger out;
+	if ( transformer == null )
+	    out = instance().getMLogger( cl );
+	else
+	    {
+		String xname = transformer.transformName( cl );
+		if (xname != null)
+		    out = instance().getMLogger( xname );
+		else
+		    out = instance().getMLogger( cl );
+	    }
+	return out;
+    }
+
+    public static MLogger getLogger()
+    {
+	MLogger out;
+	if ( transformer == null )
+	    out = instance().getMLogger();
+	else
+	    {
+		String xname = transformer.transformName();
+		if (xname != null)
+		    out = instance().getMLogger( xname );
+		else
+		    out = instance().getMLogger();
+	    }
+	return out;
+    }
+
+    public static void log(MLevel l, String msg)
+    { instance().getLogger().log( l, msg ); }
+
+    public static void log(MLevel l, String msg, Object param)
+    { instance().getLogger().log( l, msg, param ); }
+
+    public static void log(MLevel l,String msg, Object[] params)
+    { instance().getLogger().log( l, msg, params ); }
+
+    public static void log(MLevel l, String msg,Throwable t)
+    { instance().getLogger().log( l, msg, t ); }
+
+    public static void logp(MLevel l, String srcClass, String srcMeth, String msg)
+    { instance().getLogger().logp( l, srcClass, srcMeth, msg ); }
+
+    public static void logp(MLevel l, String srcClass, String srcMeth, String msg, Object param)
+    { instance().getLogger().logp( l, srcClass, srcMeth, msg, param ); }
+
+    public static void logp(MLevel l, String srcClass, String srcMeth, String msg, Object[] params)
+    { instance().getLogger().logp( l, srcClass, srcMeth, msg, params ); }
+
+    public static void logp(MLevel l, String srcClass, String srcMeth, String msg, Throwable t)
+    { instance().getLogger().logp( l, srcClass, srcMeth, msg, t ); }
+
+    public static void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg)
+    { instance().getLogger().logp( l, srcClass, srcMeth, rb, msg ); }
+
+    public static void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object param)
+    { instance().getLogger().logrb( l, srcClass, srcMeth, rb, msg, param ); }
+
+    public static void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object[] params)
+    { instance().getLogger().logrb( l, srcClass, srcMeth, rb, msg, params ); }
+
+    public static void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Throwable t)
+    { instance().getLogger().logrb( l, srcClass, srcMeth, rb, msg, t ); }
+
+    public static void entering(String srcClass, String srcMeth)
+    { instance().getLogger().entering( srcClass, srcMeth ); }
+
+    public static void entering(String srcClass, String srcMeth, Object param)
+    { instance().getLogger().entering( srcClass, srcMeth, param ); }
+
+    public static void entering(String srcClass, String srcMeth, Object params[])
+    { instance().getLogger().entering( srcClass, srcMeth, params ); }
+
+    public static void exiting(String srcClass, String srcMeth)
+    { instance().getLogger().exiting( srcClass, srcMeth ); }
+
+    public static void exiting(String srcClass, String srcMeth, Object result)
+    { instance().getLogger().exiting( srcClass, srcMeth, result ); }
+
+    public static void throwing(String srcClass, String srcMeth, Throwable t)
+    { instance().getLogger().throwing( srcClass, srcMeth, t); }
+
+    public static void severe(String msg)
+    { instance().getLogger().severe( msg ); }
+
+    public static void warning(String msg)
+    { instance().getLogger().warning( msg ); }
+
+    public static void info(String msg)
+    { instance().getLogger().info( msg ); }
+
+    public static void config(String msg)
+    { instance().getLogger().config( msg ); }
+
+    public static void fine(String msg)
+    { instance().getLogger().fine( msg ); }
+
+    public static void finer(String msg)
+    { instance().getLogger().finer( msg ); }
+
+    public static void finest(String msg)
+    { instance().getLogger().finest( msg ); }
+
+    public abstract MLogger getMLogger(String name);
+    public abstract MLogger getMLogger(Class cl);
+    public abstract MLogger getMLogger();
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/log/MLogClasses.java b/src/classes/com/mchange/v2/log/MLogClasses.java
new file mode 100644
index 0000000..6b32283
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/MLogClasses.java
@@ -0,0 +1,36 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log;
+
+public final class MLogClasses
+{
+    public final static String[] CLASSNAMES = 
+    {
+	"com.mchange.v2.log.jdk14logging.Jdk14MLog",
+	"com.mchange.v2.log.FallbackMLog"
+    };
+
+    private MLogClasses()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/log/MLogger.java b/src/classes/com/mchange/v2/log/MLogger.java
new file mode 100644
index 0000000..fa6cdc8
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/MLogger.java
@@ -0,0 +1,78 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log;
+
+import java.util.*;
+
+/**
+ * This is an interface designed to wrap around the JDK1.4 logging API, without
+ * having any compilation dependencies on that API, so that applications that use
+ * MLogger in a non JDK1.4 environment, or where some other logging library is
+ * prefrerred, may do so.
+ *
+ * Calls to handler and filter related methods may be ignored if some logging
+ * system besides jdk1.4 logging is the underlying library.
+ */
+public interface MLogger
+{
+    public ResourceBundle getResourceBundle();
+    public String getResourceBundleName();
+    public void setFilter(Object java14Filter) throws SecurityException;
+    public Object getFilter();
+    public void log(MLevel l, String msg);
+    public void log(MLevel l, String msg, Object param);
+    public void log(MLevel l,String msg, Object[] params);
+    public void log(MLevel l, String msg,Throwable t);
+    public void logp(MLevel l, String srcClass, String srcMeth, String msg);
+    public void logp(MLevel l, String srcClass, String srcMeth, String msg, Object param);
+    public void logp(MLevel l, String srcClass, String srcMeth, String msg, Object[] params);
+    public void logp(MLevel l, String srcClass, String srcMeth, String msg, Throwable t);
+    public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg);
+    public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object param);
+    public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object[] params);
+    public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Throwable t);
+    public void entering(String srcClass, String srcMeth);
+    public void entering(String srcClass, String srcMeth, Object param);
+    public void entering(String srcClass, String srcMeth, Object params[]);
+    public void exiting(String srcClass, String srcMeth);
+    public void exiting(String srcClass, String srcMeth, Object result);
+    public void throwing(String srcClass, String srcMeth, Throwable t);
+    public void severe(String msg);
+    public void warning(String msg);
+    public void info(String msg);
+    public void config(String msg);
+    public void fine(String msg);
+    public void finer(String msg);
+    public void finest(String msg);
+    public void setLevel(MLevel l) throws SecurityException;
+    public MLevel getLevel();
+    public boolean isLoggable(MLevel l);
+    public String getName();
+    public void addHandler(Object h) throws SecurityException;
+    public void removeHandler(Object h) throws SecurityException;
+    public Object[] getHandlers();
+    public void setUseParentHandlers(boolean uph);
+    public boolean getUseParentHandlers();
+ }
+
diff --git a/src/classes/com/mchange/v2/log/NameTransformer.java b/src/classes/com/mchange/v2/log/NameTransformer.java
new file mode 100644
index 0000000..e4e72e4
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/NameTransformer.java
@@ -0,0 +1,41 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log;
+
+/**
+ * <p>When the methods return a name, the log requested from MLog.getLogger( XXX )
+ * the logger actually acquired will be based on the String returned.</p>
+ *
+ * <p>When the methods return null, no transformation will occur, and the logger
+ * that would have been returned without a transformer will be returned.</p>
+ *
+ * <p>Implementing classes must have public, no-arg constructors, through which
+ * they will be instantiated.</p>
+ */
+public interface NameTransformer
+{
+    public String transformName( String name );
+    public String transformName( Class cl );
+    public String transformName();
+}
diff --git a/src/classes/com/mchange/v2/log/PackageNames.java b/src/classes/com/mchange/v2/log/PackageNames.java
new file mode 100644
index 0000000..7a71900
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/PackageNames.java
@@ -0,0 +1,43 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log;
+
+public class PackageNames implements NameTransformer
+{
+    public String transformName( String name )
+    { return null; }
+
+    public String transformName( Class cl )
+    {
+	String fqcn = cl.getName();
+	int i = fqcn.lastIndexOf('.');
+	if (i <= 0)
+	    return "";
+	else
+	    return fqcn.substring(0,i);
+    }
+
+    public String transformName()
+    { return null; }
+}
diff --git a/src/classes/com/mchange/v2/log/jdk14logging/Jdk14MLog.java b/src/classes/com/mchange/v2/log/jdk14logging/Jdk14MLog.java
new file mode 100644
index 0000000..a194bb1
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/jdk14logging/Jdk14MLog.java
@@ -0,0 +1,390 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log.jdk14logging;
+
+import java.util.*;
+import java.util.logging.*;
+import com.mchange.v2.log.*;
+import com.mchange.v2.util.DoubleWeakHashMap;
+
+public final class Jdk14MLog extends MLog
+{
+    private static String[] UNKNOWN_ARRAY = new String[] {"UNKNOWN_CLASS", "UNKNOWN_METHOD"};
+
+    private final static String CHECK_CLASS = "java.util.logging.Logger";
+
+    private final Map namedLoggerMap = new DoubleWeakHashMap();
+
+    MLogger global = null;
+
+    public Jdk14MLog() throws ClassNotFoundException
+    { Class.forName( CHECK_CLASS ); }
+
+    public synchronized MLogger getMLogger(String name)
+    {
+        name = name.intern();
+
+        MLogger out = (MLogger) namedLoggerMap.get( name );
+        if (out == null)
+        {
+            Logger lg = Logger.getLogger(name);
+            out = new Jdk14MLogger( lg ); 
+            namedLoggerMap.put( name, out );
+        }
+        return out;
+    }
+
+    public synchronized MLogger getMLogger(Class cl)
+    { return getLogger( cl.getName() ); }
+
+
+    public synchronized MLogger getMLogger()
+    {
+        if (global == null)
+            global = new Jdk14MLogger( LogManager.getLogManager().getLogger("global") );
+        return global;
+    }
+
+    /*
+     * We have to do this ourselves when class and method aren't provided, 
+     * because the automatic extraction of this information will find the
+     * (not very informative) calls in this class.
+     */
+    private static String[] findCallingClassAndMethod()
+    {
+        StackTraceElement[] ste = new Throwable().getStackTrace();
+        for (int i = 0, len = ste.length; i < len; ++i)
+        {
+            StackTraceElement check = ste[i];
+            String cn = check.getClassName();
+            if (cn != null && ! cn.startsWith("com.mchange.v2.log.jdk14logging"))
+                return new String[] { check.getClassName(), check.getMethodName() };
+        }
+        return UNKNOWN_ARRAY;
+    }
+
+
+
+    private final static class Jdk14MLogger implements MLogger
+    {
+        volatile Logger logger;
+
+        Jdk14MLogger( Logger logger )
+        { 
+            this.logger = logger; 
+            //System.err.println("LOGGER: " + this.logger);
+        }
+
+        private static Level level(MLevel lvl)
+        { return (Level) lvl.asJdk14Level(); }
+
+        public ResourceBundle getResourceBundle()
+        { return logger.getResourceBundle(); }
+
+        public String getResourceBundleName()
+        { return logger.getResourceBundleName(); }
+
+        public void setFilter(Object java14Filter) throws SecurityException
+        {
+            if (! (java14Filter instanceof Filter))
+                throw new IllegalArgumentException("MLogger.setFilter( ... ) requires a java.util.logging.Filter. " +
+                "This is not enforced by the compiler only to permit building under jdk 1.3");
+            logger.setFilter( (Filter) java14Filter ); 
+        }
+
+        public Object getFilter()
+        { return logger.getFilter(); }
+
+        public void log(MLevel l, String msg)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( level(l), sa[0], sa[1], msg );
+        }
+
+        public void log(MLevel l, String msg, Object param)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( level(l), sa[0], sa[1], msg, param );
+        }
+
+        public void log(MLevel l,String msg, Object[] params)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( level(l), sa[0], sa[1], msg, params );
+        }
+
+        public void log(MLevel l, String msg, Throwable t)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( level(l), sa[0], sa[1], msg, t );
+        }
+
+        public void logp(MLevel l, String srcClass, String srcMeth, String msg)
+        {
+            if (! logger.isLoggable( level(l) )) return;
+
+            if (srcClass == null && srcMeth == null)
+            {
+                String[] sa = findCallingClassAndMethod();
+                srcClass = sa[0];
+                srcMeth = sa[1];
+            }
+            logger.logp( level(l), srcClass, srcMeth, msg ); 
+        }
+
+        public void logp(MLevel l, String srcClass, String srcMeth, String msg, Object param)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            if (srcClass == null && srcMeth == null)
+            {
+                String[] sa = findCallingClassAndMethod();
+                srcClass = sa[0];
+                srcMeth = sa[1];
+            }
+            logger.logp( level(l), srcClass, srcMeth, msg, param ); 
+        }
+
+        public void logp(MLevel l, String srcClass, String srcMeth, String msg, Object[] params)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            if (srcClass == null && srcMeth == null)
+            {
+                String[] sa = findCallingClassAndMethod();
+                srcClass = sa[0];
+                srcMeth = sa[1];
+            }
+            logger.logp( level(l), srcClass, srcMeth, msg, params ); 
+        }
+
+        public void logp(MLevel l, String srcClass, String srcMeth, String msg, Throwable t)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            if (srcClass == null && srcMeth == null)
+            {
+                String[] sa = findCallingClassAndMethod();
+                srcClass = sa[0];
+                srcMeth = sa[1];
+            }
+            logger.logp( level(l), srcClass, srcMeth, msg, t ); 
+        }
+
+        public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            if (srcClass == null && srcMeth == null)
+            {
+                String[] sa = findCallingClassAndMethod();
+                srcClass = sa[0];
+                srcMeth = sa[1];
+            }
+            logger.logrb( level(l), srcClass, srcMeth, rb, msg ); 
+        }
+
+        public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object param)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            if (srcClass == null && srcMeth == null)
+            {
+                String[] sa = findCallingClassAndMethod();
+                srcClass = sa[0];
+                srcMeth = sa[1];
+            }
+            logger.logrb( level(l), srcClass, srcMeth, rb, msg, param ); 
+        }
+
+        public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object[] params)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            if (srcClass == null && srcMeth == null)
+            {
+                String[] sa = findCallingClassAndMethod();
+                srcClass = sa[0];
+                srcMeth = sa[1];
+            }
+            logger.logrb( level(l), srcClass, srcMeth, rb, msg, params ); 
+        }
+
+        public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Throwable t)
+        { 
+            if (! logger.isLoggable( level(l) )) return;
+
+            if (srcClass == null && srcMeth == null)
+            {
+                String[] sa = findCallingClassAndMethod();
+                srcClass = sa[0];
+                srcMeth = sa[1];
+            }
+            logger.logrb( level(l), srcClass, srcMeth, rb, msg, t ); 
+        }
+
+        public void entering(String srcClass, String srcMeth)
+        { 
+            if (! logger.isLoggable( Level.FINER )) return;
+
+            logger.entering( srcClass, srcMeth ); 
+        }
+
+        public void entering(String srcClass, String srcMeth, Object param)
+        { 
+            if (! logger.isLoggable( Level.FINER )) return;
+
+            logger.entering( srcClass, srcMeth, param ); 
+        }
+
+        public void entering(String srcClass, String srcMeth, Object params[])
+        { 
+            if (! logger.isLoggable( Level.FINER )) return;
+
+            logger.entering( srcClass, srcMeth, params ); 
+        }
+
+        public void exiting(String srcClass, String srcMeth)
+        { 
+            if (! logger.isLoggable( Level.FINER )) return;
+
+            logger.exiting( srcClass, srcMeth ); 
+        }
+
+        public void exiting(String srcClass, String srcMeth, Object result)
+        { 
+            if (! logger.isLoggable( Level.FINER )) return;
+
+            logger.exiting( srcClass, srcMeth, result ); 
+        }
+
+        public void throwing(String srcClass, String srcMeth, Throwable t)
+        { 
+            if (! logger.isLoggable( Level.FINER )) return;
+
+            logger.throwing( srcClass, srcMeth, t ); 
+        }
+
+        public void severe(String msg)
+        { 
+            if (! logger.isLoggable( Level.SEVERE )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( Level.SEVERE, sa[0], sa[1], msg );
+        }
+
+        public void warning(String msg)
+        { 
+            if (! logger.isLoggable( Level.WARNING )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( Level.WARNING, sa[0], sa[1], msg );
+        }
+
+        public void info(String msg)
+        { 
+            if (! logger.isLoggable( Level.INFO )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( Level.INFO, sa[0], sa[1], msg );
+        }
+
+        public void config(String msg)
+        {
+            if (! logger.isLoggable( Level.CONFIG )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( Level.CONFIG, sa[0], sa[1], msg );
+        }
+
+        public void fine(String msg)
+        { 
+            if (! logger.isLoggable( Level.FINE )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( Level.FINE, sa[0], sa[1], msg );
+        }
+
+        public void finer(String msg)
+        { 
+            if (! logger.isLoggable( Level.FINER )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( Level.FINER, sa[0], sa[1], msg );
+        }
+
+        public void finest(String msg)
+        { 
+            if (! logger.isLoggable( Level.FINEST )) return;
+
+            String[] sa = findCallingClassAndMethod();
+            logger.logp( Level.FINEST, sa[0], sa[1], msg );
+        }
+
+        public void setLevel(MLevel l) throws SecurityException
+        { logger.setLevel( level(l) ); }
+
+        public MLevel getLevel()
+        { return MLevel.fromIntValue( logger.getLevel().intValue() ); }
+
+        public boolean isLoggable(MLevel l)
+        { return logger.isLoggable( level(l) ); }
+
+        public String getName()
+        { return logger.getName(); }
+
+        public void addHandler(Object h) throws SecurityException
+        { 
+            if (! (h instanceof Handler))
+                throw new IllegalArgumentException("MLogger.addHandler( ... ) requires a java.util.logging.Handler. " +
+                "This is not enforced by the compiler only to permit building under jdk 1.3");
+            logger.addHandler( (Handler) h ); 
+        }
+
+        public void removeHandler(Object h) throws SecurityException
+        {
+            if (! (h instanceof Handler))
+                throw new IllegalArgumentException("MLogger.removeHandler( ... ) requires a java.util.logging.Handler. " +
+                "This is not enforced by the compiler only to permit building under jdk 1.3");
+            logger.removeHandler( (Handler) h ); 
+        }
+
+        public Object[] getHandlers()
+        { return logger.getHandlers(); }
+
+        public void setUseParentHandlers(boolean uph)
+        { logger.setUseParentHandlers( uph ); }
+
+        public boolean getUseParentHandlers()
+        { return logger.getUseParentHandlers(); }
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/log/log4j/Log4jMLog.java b/src/classes/com/mchange/v2/log/log4j/Log4jMLog.java
new file mode 100644
index 0000000..4436319
--- /dev/null
+++ b/src/classes/com/mchange/v2/log/log4j/Log4jMLog.java
@@ -0,0 +1,313 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.log.log4j;
+
+import java.text.*;
+import java.util.*;
+
+import com.mchange.v2.log.*;
+import com.mchange.v2.util.DoubleWeakHashMap;
+
+import org.apache.log4j.*;
+
+public final class Log4jMLog extends MLog
+{
+    final static String CHECK_CLASS = "org.apache.log4j.Logger";
+
+    MLogger global = null;
+
+    public Log4jMLog() throws ClassNotFoundException
+    { Class.forName( CHECK_CLASS ); }
+
+    public MLogger getMLogger(String name)
+    {
+        Logger lg = Logger.getLogger(name);
+        return new Log4jMLogger( lg ); 
+    }
+
+    public MLogger getMLogger(Class cl)
+    { 
+        Logger lg = Logger.getLogger(cl);
+        return new Log4jMLogger( lg );
+    }
+
+
+    public MLogger getMLogger()
+    {
+        Logger lg = Logger.getRootLogger();
+        return new Log4jMLogger( lg ); 
+    }
+
+    private final static class Log4jMLogger implements MLogger
+    {
+        final static String FQCN = Log4jMLogger.class.getName();
+
+        // protected by this' lock
+        MLevel myLevel = null;
+        
+        volatile Logger logger;
+
+        Log4jMLogger( Logger logger )
+        { this.logger = logger; }
+
+        private static MLevel guessMLevel(Level lvl)
+        {
+            if (lvl == null)
+                return null;
+            else if (lvl == Level.ALL)
+                return MLevel.ALL;
+            else if (lvl == Level.DEBUG)
+                return MLevel.FINEST;
+            else if (lvl == Level.ERROR)
+                return MLevel.SEVERE;
+            else if (lvl == Level.FATAL)
+                return MLevel.SEVERE;
+            else if (lvl == Level.INFO)
+                return MLevel.INFO;
+            else if (lvl == Level.OFF)
+                return MLevel.OFF;
+            else if (lvl == Level.WARN)
+                return MLevel.WARNING;
+            else
+                throw new IllegalArgumentException("Unknown level: " + lvl);
+        }
+
+        private static Level level(MLevel lvl)
+        {
+            if (lvl == null)
+                return null;
+            else if (lvl == MLevel.ALL)
+                return Level.ALL;
+            else if (lvl == MLevel.CONFIG)
+                return Level.DEBUG;
+            else if (lvl == MLevel.FINE)
+                return Level.DEBUG;
+            else if (lvl == MLevel.FINER)
+                return Level.DEBUG;
+            else if (lvl == MLevel.FINEST)
+                return Level.DEBUG;
+            else if (lvl == MLevel.INFO)
+                return Level.INFO;
+            else if (lvl == MLevel.INFO)
+                return Level.OFF;
+            else if (lvl == MLevel.SEVERE)
+                return Level.ERROR;
+            else if (lvl == MLevel.WARNING)
+                return Level.WARN;
+            else
+                throw new IllegalArgumentException("Unknown MLevel: " + lvl);
+        }
+
+        private static String createMessage(String srcClass, String srcMeth, String msg)
+        {
+            StringBuffer sb = new StringBuffer(511);
+            sb.append("[class: ");
+            sb.append( srcClass );
+            sb.append("; method: ");
+            sb.append( srcMeth );
+            if (! srcMeth.endsWith(")"))
+                sb.append("()");
+            sb.append("] ");
+            sb.append( msg );
+            return sb.toString();
+        }
+
+        private static String createMessage(String srcMeth, String msg)
+        {
+            StringBuffer sb = new StringBuffer(511);
+            sb.append("[method: ");
+            sb.append( srcMeth );
+            if (! srcMeth.endsWith(")"))
+                sb.append("()");
+            sb.append("] ");
+            sb.append( msg );
+            return sb.toString();
+        }
+
+        public ResourceBundle getResourceBundle()
+        { return null; }
+
+        public String getResourceBundleName()
+        { return null; }
+
+        public void setFilter(Object java14Filter) throws SecurityException
+        { warning("setFilter() not supported by MLogger " + this.getClass().getName()); }
+
+        public Object getFilter()
+        { return null; }
+
+        private void log(Level lvl, Object msg, Throwable t)
+        { logger.log( FQCN, lvl, msg, t ); }
+
+        public void log(MLevel l, String msg)
+        { log( level(l),  msg,  null); }
+
+        public void log(MLevel l, String msg, Object param)
+        { log( level(l),  (msg!=null ? MessageFormat.format(msg, new Object[] { param }) : null),  null); }
+
+        public void log(MLevel l,String msg, Object[] params)
+        { log( level(l),  (msg!=null ? MessageFormat.format(msg, params) : null),  null); }
+
+        public void log(MLevel l, String msg, Throwable t)
+        { log( level(l),  msg,  t); }
+
+        public void logp(MLevel l, String srcClass, String srcMeth, String msg)
+        { log( level(l),  createMessage( srcClass, srcMeth, msg),  null); }
+
+        public void logp(MLevel l, String srcClass, String srcMeth, String msg, Object param)
+        { log( level(l),  createMessage( srcClass, srcMeth, (msg!=null ? MessageFormat.format(msg, new Object[] {param}) : null) ),  null); }
+
+        public void logp(MLevel l, String srcClass, String srcMeth, String msg, Object[] params)
+        { log( level(l),  createMessage( srcClass, srcMeth, (msg!=null ? MessageFormat.format(msg, params) : null) ),  null); }
+
+        public void logp(MLevel l, String srcClass, String srcMeth, String msg, Throwable t)
+        { log( level(l),  createMessage( srcClass, srcMeth, msg ),  t); }
+
+        public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg)
+        { log( level(l),  createMessage( srcClass, srcMeth, formatMessage(rb, msg, null) ),  null); }
+
+        public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object param)
+        { log( level(l),  createMessage( srcClass, srcMeth, formatMessage(rb, msg, new Object[] { param } ) ),  null); }
+
+        public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Object[] params)
+        { log( level(l),  createMessage( srcClass, srcMeth, formatMessage(rb, msg, params) ),  null); }
+
+        public void logrb(MLevel l, String srcClass, String srcMeth, String rb, String msg, Throwable t)
+        { log( level(l),  createMessage( srcClass, srcMeth, formatMessage(rb, msg, null) ),  t); }
+
+        public void entering(String srcClass, String srcMeth)
+        { log( Level.DEBUG,  createMessage( srcClass, srcMeth, "entering method." ),  null); }
+
+        public void entering(String srcClass, String srcMeth, Object param)
+        { log( Level.DEBUG,  createMessage( srcClass, srcMeth, "entering method... param: " + param.toString() ),  null); }
+
+        public void entering(String srcClass, String srcMeth, Object params[])
+        { log( Level.DEBUG,  createMessage( srcClass, srcMeth, "entering method... " + LogUtils.createParamsList( params ) ),  null); }
+
+        public void exiting(String srcClass, String srcMeth)
+        { log( Level.DEBUG,  createMessage( srcClass, srcMeth, "exiting method." ),  null); }
+
+        public void exiting(String srcClass, String srcMeth, Object result)
+        { log( Level.DEBUG,  createMessage( srcClass, srcMeth, "exiting method... result: " + result.toString() ),  null); }
+
+        public void throwing(String srcClass, String srcMeth, Throwable t)
+        { log( Level.DEBUG,  createMessage( srcClass, srcMeth, "throwing exception... " ),  t); }
+
+        public void severe(String msg)
+        { log( Level.ERROR, msg,  null); }
+
+        public void warning(String msg)
+        { log( Level.WARN, msg,  null); }
+
+        public void info(String msg)
+        { log( Level.INFO, msg,  null); }
+
+        public void config(String msg)
+        { log( Level.DEBUG, msg,  null); }
+
+        public void fine(String msg)
+        { log( Level.DEBUG, msg,  null); }
+
+        public void finer(String msg)
+        { log( Level.DEBUG, msg,  null); }
+
+        public void finest(String msg)
+        { log( Level.DEBUG, msg,  null); }
+
+        public synchronized void setLevel(MLevel l) throws SecurityException
+        {
+            logger.setLevel( level( l ) );
+            myLevel = l;
+        }
+
+        public synchronized MLevel getLevel()
+        { 
+            //System.err.println( logger.getLevel() );
+            if (myLevel == null)
+                myLevel = guessMLevel( logger.getLevel() );
+            return myLevel;
+        }
+
+        public boolean isLoggable(MLevel l)
+        { 
+            //System.err.println( "MLevel: " + l + "; isEnabledFor(): " + logger.isEnabledFor( level(l) ) + "; getLevel(): " + getLevel() +
+            //"; MLog.getLogger().getLevel(): " + MLog.getLogger().getLevel());
+            //new Exception("WHADDAFUC").printStackTrace();
+            return logger.isEnabledFor( level(l) );
+        }
+
+        public String getName()
+        { return logger.getName(); }
+
+        public void addHandler(Object h) throws SecurityException
+        { 
+            if (! (h instanceof Appender))
+                throw new IllegalArgumentException("The 'handler' " + h + " is not compatible with MLogger " + this); 
+            logger.addAppender( (Appender) h ); 
+        }
+
+        public void removeHandler(Object h) throws SecurityException
+        {
+            if (! (h instanceof Appender))
+                throw new IllegalArgumentException("The 'handler' " + h + " is not compatible with MLogger " + this); 
+            logger.removeAppender( (Appender) h ); 
+        }
+
+        public Object[] getHandlers()
+        {
+            List tmp = new LinkedList();
+            for (Enumeration e = logger.getAllAppenders(); e.hasMoreElements(); )
+                tmp.add( e.nextElement() );
+            return tmp.toArray();
+        }
+
+        public void setUseParentHandlers(boolean uph)
+        { logger.setAdditivity( uph ); }
+
+        public boolean getUseParentHandlers()
+        { return logger.getAdditivity(); }
+    }
+
+    private static String formatMessage( String rbname, String msg, Object[] params )
+    {
+        if ( msg == null )
+        {
+            if (params == null)
+                return "";
+            else
+                return LogUtils.createParamsList( params );
+        }
+        else
+        {
+            ResourceBundle rb = ResourceBundle.getBundle( rbname );
+            if (rb != null)
+            {
+                String check = rb.getString( msg );
+                if (check != null)
+                    msg = check;
+            }
+            return (params == null ? msg : MessageFormat.format( msg, params ));
+        }
+    } 
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/management/ManagementUtils.java b/src/classes/com/mchange/v2/management/ManagementUtils.java
new file mode 100644
index 0000000..7ca7e6f
--- /dev/null
+++ b/src/classes/com/mchange/v2/management/ManagementUtils.java
@@ -0,0 +1,96 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.management;
+
+import javax.management.*;
+import java.util.Comparator;
+
+public class ManagementUtils
+{
+    public final static Comparator PARAM_INFO_COMPARATOR = new Comparator()
+    {
+        public int compare(Object a, Object b)
+        {
+            MBeanParameterInfo aa = (MBeanParameterInfo) a;
+            MBeanParameterInfo bb = (MBeanParameterInfo) b;
+            int out = aa.getType().compareTo(bb.getType());
+            if (out == 0)
+            {
+                out = aa.getName().compareTo(bb.getName());
+                if (out == 0)
+                {
+                    String aDesc = aa.getDescription();
+                    String bDesc = bb.getDescription();
+                    if (aDesc == null && bDesc == null)
+                        out = 0;
+                    else if (aDesc == null)
+                        out = -1;
+                    else if (bDesc == null)
+                        out = 1;
+                    else
+                        out = aDesc.compareTo(bDesc);
+                }
+            }
+            return out;
+        }
+    };
+    
+    public final static Comparator OP_INFO_COMPARATOR = new Comparator()
+    {
+        public int compare(Object a, Object b)
+        {
+            MBeanOperationInfo aa = (MBeanOperationInfo) a;
+            MBeanOperationInfo bb = (MBeanOperationInfo) b;
+            String aName = aa.getName();
+            String bName = bb.getName();
+            int out = String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
+            if (out == 0)
+            {
+                if (aName.equals(bName))
+                {
+                    MBeanParameterInfo[] aParams = aa.getSignature();
+                    MBeanParameterInfo[] bParams = bb.getSignature();
+                    if (aParams.length < bParams.length)
+                        out = -1;
+                    else if (aParams.length > bParams.length)
+                        out = 1;
+                    else
+                    {
+                        for (int i = 0, len = aParams.length; i < len; ++i)
+                        {
+                            out = PARAM_INFO_COMPARATOR.compare(aParams[i], bParams[i]);
+                            if (out != 0)
+                                break;
+                        }
+                    }
+                }
+                else
+                {
+                    out = aName.compareTo(bName);
+                }
+            }
+            return out;
+        }
+    };
+}
diff --git a/src/classes/com/mchange/v2/naming/JavaBeanObjectFactory.java b/src/classes/com/mchange/v2/naming/JavaBeanObjectFactory.java
new file mode 100644
index 0000000..330cd1c
--- /dev/null
+++ b/src/classes/com/mchange/v2/naming/JavaBeanObjectFactory.java
@@ -0,0 +1,159 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.naming;
+
+import java.beans.*;
+import java.util.*;
+import javax.naming.*;
+import com.mchange.v2.log.*;
+import java.lang.reflect.Method;
+import javax.naming.spi.ObjectFactory;
+import com.mchange.v2.beans.BeansUtils;
+import com.mchange.v2.lang.Coerce;
+import com.mchange.v2.ser.SerializableUtils;
+
+public class JavaBeanObjectFactory implements ObjectFactory
+{
+    private final static MLogger logger = MLog.getLogger( JavaBeanObjectFactory.class );
+
+    final static Object NULL_TOKEN = new Object();
+
+    public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env)
+	throws Exception
+    {
+	if (refObj instanceof Reference)
+	    {
+		Reference ref = (Reference) refObj;
+		Map refAddrsMap = new HashMap();
+		for (Enumeration e = ref.getAll(); e.hasMoreElements(); )
+		    {
+			RefAddr addr = (RefAddr) e.nextElement();
+			refAddrsMap.put( addr.getType(), addr );
+		    }
+		Class beanClass = Class.forName( ref.getClassName() );
+		Set refProps = null;
+		RefAddr refPropsRefAddr = (BinaryRefAddr) refAddrsMap.remove( JavaBeanReferenceMaker.REF_PROPS_KEY );
+		if ( refPropsRefAddr != null )
+		    refProps = (Set) SerializableUtils.fromByteArray( (byte[]) refPropsRefAddr.getContent() );
+		Map propMap = createPropertyMap( beanClass, refAddrsMap );
+		return findBean( beanClass, propMap, refProps );
+	    }
+	else
+	    return null;
+    }
+
+    private Map createPropertyMap( Class beanClass, Map refAddrsMap ) throws Exception
+    {
+	BeanInfo bi = Introspector.getBeanInfo( beanClass );
+	PropertyDescriptor[] pds = bi.getPropertyDescriptors();
+
+	Map out = new HashMap();
+	for (int i = 0, len = pds.length; i < len; ++i)
+	    {
+		PropertyDescriptor pd = pds[i];
+		String propertyName = pd.getName();
+		Class  propertyType = pd.getPropertyType();
+		Object addr = refAddrsMap.remove( propertyName );
+		if (addr != null)
+		    {
+			if ( addr instanceof StringRefAddr )
+			    {
+				String content = (String) ((StringRefAddr) addr).getContent();
+				if ( Coerce.canCoerce( propertyType ) )
+				    out.put( propertyName, Coerce.toObject( content, propertyType ) );
+				else
+				    {
+					PropertyEditor pe = BeansUtils.findPropertyEditor( pd );
+					pe.setAsText( content );
+					out.put( propertyName, pe.getValue() );
+				    }
+			    }
+			else if ( addr instanceof BinaryRefAddr )
+			    {
+				byte[] content = (byte[]) ((BinaryRefAddr) addr).getContent();
+				if ( content.length == 0 )
+				    out.put( propertyName, NULL_TOKEN ); //we use an empty array to mean null
+				else
+				    out.put( propertyName, SerializableUtils.fromByteArray( content ) ); //this will handle "indirectly serialized" objects.
+			    }
+			else
+			    {
+				if (logger.isLoggable( MLevel.WARNING ))
+				    logger.warning(this.getClass().getName() + " -- unknown RefAddr subclass: " + addr.getClass().getName());
+			    }
+		    }
+	    }
+	for ( Iterator ii = refAddrsMap.keySet().iterator(); ii.hasNext(); )
+	    {
+		String type = (String) ii.next();
+		if (logger.isLoggable( MLevel.WARNING ))
+		    logger.warning(this.getClass().getName() + " -- RefAddr for unknown property: " + type);
+	    }
+	return out;
+    }
+
+    protected Object createBlankInstance(Class beanClass) throws Exception
+    { return beanClass.newInstance(); }
+
+    protected Object findBean(Class beanClass, Map propertyMap, Set refProps ) throws Exception
+    {
+	Object bean = createBlankInstance( beanClass );
+	BeanInfo bi = Introspector.getBeanInfo( bean.getClass() );
+	PropertyDescriptor[] pds = bi.getPropertyDescriptors();
+	
+	for (int i = 0, len = pds.length; i < len; ++i)
+	    {
+		PropertyDescriptor pd = pds[i];
+		String propertyName = pd.getName();
+		Object value = propertyMap.get( propertyName );
+		Method setter = pd.getWriteMethod();
+		if (value != null)
+		    {
+			if (setter != null)
+			    setter.invoke( bean, new Object[] { (value == NULL_TOKEN ? null : value) } );
+			else
+			    {
+				//System.err.println(this.getClass().getName() + ": Could not restore read-only property '" + propertyName + "'.");
+				if (logger.isLoggable( MLevel.WARNING ))
+				    logger.warning(this.getClass().getName() + ": Could not restore read-only property '" + propertyName + "'.");
+			    }
+		    }
+		else
+		    {
+			if (setter != null)
+			    {
+				if (refProps == null || refProps.contains( propertyName ))
+				    {
+					//System.err.println(this.getClass().getName() +
+					//": WARNING -- Expected writable property '" + propertyName + "' left at default value");
+					if (logger.isLoggable( MLevel.WARNING ))
+					    logger.warning(this.getClass().getName() + " -- Expected writable property ''" + propertyName + "'' left at default value");
+				    }
+			    }
+		    }
+	    }
+	
+	return bean;
+    }
+}
diff --git a/src/classes/com/mchange/v2/naming/JavaBeanReferenceMaker.java b/src/classes/com/mchange/v2/naming/JavaBeanReferenceMaker.java
new file mode 100644
index 0000000..d9b715e
--- /dev/null
+++ b/src/classes/com/mchange/v2/naming/JavaBeanReferenceMaker.java
@@ -0,0 +1,176 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.naming;
+
+import java.beans.*;
+import java.io.*;
+import java.util.*;
+import javax.naming.*;
+import com.mchange.v2.log.*;
+import java.lang.reflect.Method;
+import com.mchange.v2.lang.Coerce;
+import com.mchange.v2.beans.BeansUtils;
+import com.mchange.v2.ser.SerializableUtils;
+import com.mchange.v2.ser.IndirectPolicy;
+
+public class JavaBeanReferenceMaker implements ReferenceMaker
+{
+    private final static MLogger logger = MLog.getLogger( JavaBeanReferenceMaker.class );
+
+    final static String REF_PROPS_KEY = "com.mchange.v2.naming.JavaBeanReferenceMaker.REF_PROPS_KEY";
+
+    final static Object[] EMPTY_ARGS = new Object[0];
+
+    final static byte[] NULL_TOKEN_BYTES = new byte[0];
+
+    String factoryClassName = "com.mchange.v2.naming.JavaBeanObjectFactory";
+    String defaultFactoryClassLocation = null;
+
+    Set referenceProperties = new HashSet();
+
+    ReferenceIndirector indirector = new ReferenceIndirector();
+
+    public Hashtable getEnvironmentProperties()
+    { return indirector.getEnvironmentProperties(); }
+
+    public void setEnvironmentProperties( Hashtable environmentProperties )
+    { indirector.setEnvironmentProperties( environmentProperties ); }
+
+    public void setFactoryClassName(String factoryClassName)
+    { this.factoryClassName = factoryClassName; }
+
+    public String getFactoryClassName()
+    { return factoryClassName; }
+
+    public String getDefaultFactoryClassLocation()
+    { return defaultFactoryClassLocation; }
+
+    public void setDefaultFactoryClassLocation( String defaultFactoryClassLocation )
+    { this.defaultFactoryClassLocation = defaultFactoryClassLocation; }
+
+    public void addReferenceProperty( String propName )
+    { referenceProperties.add( propName ); }
+
+    public void removeReferenceProperty( String propName )
+    { referenceProperties.remove( propName ); }
+
+    public Reference createReference( Object bean )
+	throws NamingException
+    {
+	try
+	    {
+		BeanInfo bi = Introspector.getBeanInfo( bean.getClass() );
+		PropertyDescriptor[] pds = bi.getPropertyDescriptors();
+		List refAddrs = new ArrayList();
+		String factoryClassLocation = defaultFactoryClassLocation;
+
+		boolean using_ref_props = referenceProperties.size() > 0;
+
+		// we only include this so that on dereference we are not surprised to find some properties missing
+		if (using_ref_props)
+		    refAddrs.add( new BinaryRefAddr( REF_PROPS_KEY, SerializableUtils.toByteArray( referenceProperties ) ) );
+
+		for (int i = 0, len = pds.length; i < len; ++i)
+		    {
+			PropertyDescriptor pd = pds[i];
+			String propertyName = pd.getName();
+			//System.err.println("Making Reference: " + propertyName);
+
+			if (using_ref_props && ! referenceProperties.contains( propertyName ))
+			    {
+				//System.err.println("Not a ref_prop -- continuing.");
+				continue;
+			    }
+
+			Class  propertyType = pd.getPropertyType();
+			Method getter = pd.getReadMethod();
+			Method setter = pd.getWriteMethod();
+			if (getter != null && setter != null) //only use properties that are both readable and writable
+			    {
+				Object val = getter.invoke( bean, EMPTY_ARGS );
+				//System.err.println( "val: " + val );
+				if (propertyName.equals("factoryClassLocation"))
+				    {
+					if (String.class != propertyType)
+					    throw new NamingException(this.getClass().getName() + " requires a factoryClassLocation property to be a string, " +
+								      propertyType.getName() + " is not valid.");
+					factoryClassLocation = (String) val;
+				    }
+
+				if (val == null)
+				    {
+					RefAddr addMe = new BinaryRefAddr( propertyName, NULL_TOKEN_BYTES );
+					refAddrs.add( addMe );
+				    }
+				else if ( Coerce.canCoerce( propertyType ) )
+				    {
+					RefAddr addMe = new StringRefAddr( propertyName, String.valueOf( val ) );
+					refAddrs.add( addMe );
+				    }
+				else  //other Object properties
+				    {
+					RefAddr addMe = null;
+					PropertyEditor pe = BeansUtils.findPropertyEditor( pd );
+					if (pe != null)
+					    {
+						pe.setValue( val );
+						String textValue = pe.getAsText();
+						if (textValue != null)
+						    addMe = new StringRefAddr( propertyName, textValue );
+					    }
+					if (addMe == null) //property editor approach failed
+					    addMe = new BinaryRefAddr( propertyName, SerializableUtils.toByteArray( val, 
+														    indirector, 
+														    IndirectPolicy.INDIRECT_ON_EXCEPTION ) );
+					refAddrs.add( addMe );
+				    }
+			    }
+			else
+			    {
+// 				System.err.println(this.getClass().getName() +
+// 						   ": Skipping " + propertyName + " because it is " + (setter == null ? "read-only." : "write-only."));
+
+				if ( logger.isLoggable( MLevel.WARNING ) )
+				    logger.warning(this.getClass().getName() + ": Skipping " + propertyName + 
+						   " because it is " + (setter == null ? "read-only." : "write-only."));
+			    }
+
+		    }
+		Reference out = new Reference( bean.getClass().getName(), factoryClassName, factoryClassLocation );
+		for (Iterator ii = refAddrs.iterator(); ii.hasNext(); )
+		    out.add( (RefAddr) ii.next() );
+		return out;
+	    }
+	catch ( Exception e )
+	    {
+		//e.printStackTrace();
+		if ( Debug.DEBUG && logger.isLoggable( MLevel.FINE ) )
+		    logger.log( MLevel.FINE, "Exception trying to create Reference.", e);
+
+		throw new NamingException("Could not create reference from bean: " + e.toString() );
+	    }
+    }
+
+}
+
diff --git a/src/classes/com/mchange/v2/naming/ReferenceIndirector.java b/src/classes/com/mchange/v2/naming/ReferenceIndirector.java
new file mode 100644
index 0000000..1d08402
--- /dev/null
+++ b/src/classes/com/mchange/v2/naming/ReferenceIndirector.java
@@ -0,0 +1,117 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.naming;
+
+import java.io.InvalidObjectException;
+import java.io.IOException;
+import java.util.Hashtable;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import com.mchange.v2.ser.Indirector;
+import com.mchange.v2.ser.IndirectlySerialized;
+
+public class ReferenceIndirector implements Indirector
+{
+    final static MLogger logger = MLog.getLogger( ReferenceIndirector.class );
+
+    Name      name;
+    Name      contextName;
+    Hashtable environmentProperties;
+
+    public Name getName()
+    { return name; }
+
+    public void setName( Name name )
+    { this.name = name; }
+
+    public Name getNameContextName()
+    { return contextName; }
+
+    public void setNameContextName( Name contextName )
+    { this.contextName = contextName; }
+
+    public Hashtable getEnvironmentProperties()
+    { return environmentProperties; }
+
+    public void setEnvironmentProperties( Hashtable environmentProperties )
+    { this.environmentProperties = environmentProperties; }
+
+    public IndirectlySerialized indirectForm( Object orig ) throws Exception
+    { 
+	Reference ref = ((Referenceable) orig).getReference();
+	return new ReferenceSerialized( ref, name, contextName, environmentProperties );
+    }
+
+    private static class ReferenceSerialized implements IndirectlySerialized
+    {
+	Reference   reference;
+	Name        name;
+	Name        contextName;
+	Hashtable   env;
+
+	ReferenceSerialized( Reference   reference,
+			     Name        name,
+			     Name        contextName,
+			     Hashtable   env )
+	{
+	    this.reference = reference;
+	    this.name = name;
+	    this.contextName = contextName;
+	    this.env = env;
+	}
+
+
+	public Object getObject() throws ClassNotFoundException, IOException
+	{
+	    try
+		{
+		    Context initialContext;
+		    if ( env == null )
+			initialContext = new InitialContext();
+		    else
+			initialContext = new InitialContext( env );
+
+		    Context nameContext = null;
+		    if ( contextName != null )
+			nameContext = (Context) initialContext.lookup( contextName );
+
+		    return ReferenceableUtils.referenceToObject( reference, name, nameContext, env ); 
+		}
+	    catch (NamingException e)
+		{
+		    //e.printStackTrace();
+		    if ( logger.isLoggable( MLevel.WARNING ) )
+			logger.log( MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", e );
+		    throw new InvalidObjectException( "Failed to acquire the Context necessary to lookup an Object: " + e.toString() );
+		}
+	}
+    }
+}
diff --git a/src/classes/com/mchange/v2/naming/ReferenceMaker.java b/src/classes/com/mchange/v2/naming/ReferenceMaker.java
new file mode 100644
index 0000000..5a28a74
--- /dev/null
+++ b/src/classes/com/mchange/v2/naming/ReferenceMaker.java
@@ -0,0 +1,33 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.naming;
+
+import javax.naming.Reference;
+import javax.naming.NamingException;
+
+public interface ReferenceMaker
+{
+    public Reference createReference( Object o )
+	throws NamingException;
+}
diff --git a/src/classes/com/mchange/v2/naming/ReferenceableUtils.java b/src/classes/com/mchange/v2/naming/ReferenceableUtils.java
new file mode 100644
index 0000000..57c6a93
--- /dev/null
+++ b/src/classes/com/mchange/v2/naming/ReferenceableUtils.java
@@ -0,0 +1,177 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.naming;
+
+import java.net.*;
+import javax.naming.*;
+import com.mchange.v2.log.MLevel;
+import com.mchange.v2.log.MLog;
+import com.mchange.v2.log.MLogger;
+import javax.naming.spi.ObjectFactory;
+import java.util.Hashtable;
+
+public final class ReferenceableUtils
+{
+    final static MLogger logger = MLog.getLogger( ReferenceableUtils.class );
+
+    /* don't worry -- References can have duplicate RefAddrs (I think!) */
+    final static String REFADDR_VERSION                = "version";
+    final static String REFADDR_CLASSNAME              = "classname";
+    final static String REFADDR_FACTORY                = "factory";
+    final static String REFADDR_FACTORY_CLASS_LOCATION = "factoryClassLocation";
+    final static String REFADDR_SIZE                   = "size";
+
+    final static int CURRENT_REF_VERSION = 1;
+
+    /**
+     * A null string value in a Reference sometimes goes to the literal
+     * "null". Sigh. We convert this string to a Java null.
+     */
+    public static String literalNullToNull( String s )
+    {
+	if (s == null || "null".equals( s ))
+	    return null;
+	else
+	    return s;
+    }
+
+    public static Object referenceToObject( Reference ref, Name name, Context nameCtx, Hashtable env)
+	throws NamingException
+    {
+	try
+	    {
+		String fClassName = ref.getFactoryClassName();
+		String fClassLocation = ref.getFactoryClassLocation();
+		
+		ClassLoader cl;
+		if ( fClassLocation == null )
+		    cl = ClassLoader.getSystemClassLoader();
+		else
+		    {
+			URL u = new URL( fClassLocation );
+			cl = new URLClassLoader( new URL[] { u }, ClassLoader.getSystemClassLoader() );
+		    }
+		
+		Class fClass = Class.forName( fClassName, true, cl );
+		ObjectFactory of = (ObjectFactory) fClass.newInstance();
+		return of.getObjectInstance( ref, name, nameCtx, env );
+	    }
+	catch ( Exception e )
+	    {
+		if (Debug.DEBUG) 
+		    {
+			//e.printStackTrace();
+			if ( logger.isLoggable( MLevel.FINE ) )
+			    logger.log( MLevel.FINE, "Could not resolve Reference to Object!", e);
+		    }
+		NamingException ne = new NamingException("Could not resolve Reference to Object!");
+		ne.setRootCause( e );
+		throw ne;
+	    }
+    }
+
+    /**
+     * @deprecated nesting references seemed useful until I realized that
+     *             references are Serializable and can be stored in a BinaryRefAddr.
+     *             Oops.
+     */
+    public static void appendToReference(Reference appendTo, Reference orig)
+	throws NamingException
+    {
+	int len = orig.size();
+	appendTo.add( new StringRefAddr( REFADDR_VERSION, String.valueOf( CURRENT_REF_VERSION ) ) );
+	appendTo.add( new StringRefAddr( REFADDR_CLASSNAME, orig.getClassName() ) );
+	appendTo.add( new StringRefAddr( REFADDR_FACTORY, orig.getFactoryClassName() ) );
+	appendTo.add( new StringRefAddr( REFADDR_FACTORY_CLASS_LOCATION, 
+					 orig.getFactoryClassLocation() ) );
+	appendTo.add( new StringRefAddr( REFADDR_SIZE, String.valueOf(len) ) );
+	for (int i = 0; i < len; ++i)
+	    appendTo.add( orig.get(i) );
+    }
+
+    /**
+     * @deprecated nesting references seemed useful until I realized that
+     *             references are Serializable and can be stored in a BinaryRefAddr.
+     *             Oops.
+     */
+    public static ExtractRec extractNestedReference(Reference extractFrom, int index)
+	throws NamingException
+    {
+	try
+	    {
+		int version = Integer.parseInt((String) extractFrom.get(index++).getContent());
+		if (version == 1)
+		    {
+			String className = (String) extractFrom.get(index++).getContent();
+			String factoryClassName = (String) extractFrom.get(index++).getContent();
+			String factoryClassLocation = (String) extractFrom.get(index++).getContent();
+
+			Reference outRef = new Reference( className, 
+							  factoryClassName,
+							  factoryClassLocation );
+			int size = Integer.parseInt((String) extractFrom.get(index++).getContent());
+			for (int i = 0; i < size; ++i)
+			    outRef.add( extractFrom.get( index++ ) );
+			return new ExtractRec( outRef, index );
+		    }
+		else
+		    throw new NamingException("Bad version of nested reference!!!");
+	    }
+	catch (NumberFormatException e)
+	    {
+		if (Debug.DEBUG) 
+		    {
+			//e.printStackTrace();
+			if ( logger.isLoggable( MLevel.FINE ) )
+			    logger.log( MLevel.FINE, "Version or size nested reference was not a number!!!", e);
+		    }
+		throw new NamingException("Version or size nested reference was not a number!!!"); 
+	    }
+    }
+
+    /**
+     * @deprecated nesting references seemed useful until I realized that
+     *             references are Serializable and can be stored in a BinaryRefAddr.
+     *             Oops.
+     */
+    public static class ExtractRec
+    {
+	public Reference ref;
+
+	/**
+	 *  return the first RefAddr index that the function HAS NOT read to
+	 *  extract the reference.
+	 */
+	public int       index;
+
+	private ExtractRec(Reference ref, int index)
+	{
+	    this.ref   = ref;
+	    this.index = index;
+	}
+    }
+
+    private ReferenceableUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/resourcepool/BasicResourcePool.java b/src/classes/com/mchange/v2/resourcepool/BasicResourcePool.java
new file mode 100644
index 0000000..80ec271
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/BasicResourcePool.java
@@ -0,0 +1,2085 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+import java.util.*;
+import com.mchange.v2.async.*;
+import com.mchange.v2.log.*;
+import com.mchange.v2.lang.ThreadUtils;
+import com.mchange.v2.util.ResourceClosedException;
+
+class BasicResourcePool implements ResourcePool
+{
+    private final static MLogger logger = MLog.getLogger( BasicResourcePool.class );
+
+    final static int AUTO_CULL_FREQUENCY_DIVISOR = 4;
+    final static int AUTO_MAX_CULL_FREQUENCY = (15 * 60 * 1000); //15 mins
+    final static int AUTO_MIN_CULL_FREQUENCY = (1 * 1000); //15 mins
+
+
+    //XXX: temporary -- for selecting between AcquireTask types
+    //     remove soon, and use only ScatteredAcquireTask,
+    //     presuming no problems appear
+    final static String USE_SCATTERED_ACQUIRE_TASK_KEY = "com.mchange.v2.resourcepool.experimental.useScatteredAcquireTask";
+    final static boolean USE_SCATTERED_ACQUIRE_TASK;
+    static
+    {
+        String checkScattered = com.mchange.v2.cfg.MultiPropertiesConfig.readVmConfig().getProperty(USE_SCATTERED_ACQUIRE_TASK_KEY);
+        if (checkScattered != null && checkScattered.trim().toLowerCase().equals("true"))
+        {
+            USE_SCATTERED_ACQUIRE_TASK = true;
+            if ( logger.isLoggable( MLevel.INFO ) )
+                logger.info(BasicResourcePool.class.getName() + " using experimental ScatteredAcquireTask.");
+        }
+        else
+            USE_SCATTERED_ACQUIRE_TASK = false;
+    }
+    // end temporary switch between acquire task types
+
+    //MT: unchanged post c'tor
+    final Manager mgr;
+
+    final int start;
+    final int min;
+    final int max;
+    final int inc;
+
+    final int num_acq_attempts;
+    final int acq_attempt_delay;
+
+    final long check_idle_resources_delay;       //milliseconds
+    final long max_resource_age;                 //milliseconds
+    final long max_idle_time;                    //milliseconds
+    final long excess_max_idle_time;             //milliseconds
+    final long destroy_unreturned_resc_time;     //milliseconds
+    final long expiration_enforcement_delay;     //milliseconds
+
+    final boolean break_on_acquisition_failure;
+    final boolean debug_store_checkout_exceptions;
+
+    final long pool_start_time = System.currentTimeMillis();
+
+    //MT: not-reassigned, thread-safe, and independent
+    final BasicResourcePoolFactory factory;
+    final AsynchronousRunner       taskRunner;
+    final RunnableQueue            asyncEventQueue;
+    final ResourcePoolEventSupport rpes;
+
+    //MT: protected by this' lock
+    Timer                    cullAndIdleRefurbishTimer;
+    TimerTask                cullTask;
+    TimerTask                idleRefurbishTask;
+    HashSet                  acquireWaiters = new HashSet();
+    HashSet                  otherWaiters = new HashSet();
+
+    int pending_acquires;
+    int pending_removes;
+
+    int target_pool_size;
+
+    /*  keys are all valid, managed resources, value is a PunchCard */ 
+    HashMap  managed = new HashMap();
+
+    /* all valid, managed resources currently available for checkout */
+    LinkedList unused = new LinkedList();
+
+    /* resources which have been invalidated somehow, but which are */
+    /* still checked out and in use.                                */
+    HashSet  excluded = new HashSet();
+
+    Map formerResources = new WeakHashMap();
+
+    Set idleCheckResources = new HashSet();
+
+    boolean force_kill_acquires = false;
+
+    boolean broken = false;
+
+//  long total_acquired = 0;
+
+    long failed_checkins   = 0;
+    long failed_checkouts  = 0;
+    long failed_idle_tests = 0;
+
+    Throwable lastCheckinFailure      = null;
+    Throwable lastCheckoutFailure     = null;
+    Throwable lastIdleTestFailure     = null;
+    Throwable lastResourceTestFailure = null;
+
+    Throwable lastAcquisitionFailiure = null;
+
+    //DEBUG only!
+    Object exampleResource;
+
+    public long getStartTime()
+    { return pool_start_time; }
+
+    public long getUpTime()
+    { return System.currentTimeMillis() - pool_start_time; }
+
+    public synchronized long getNumFailedCheckins()
+    { return failed_checkins; }
+
+    public synchronized long getNumFailedCheckouts()
+    { return failed_checkouts; }
+
+    public synchronized long getNumFailedIdleTests()
+    { return failed_idle_tests; }
+
+    public synchronized Throwable getLastCheckinFailure()
+    { return lastCheckinFailure; }
+
+    //must be called from a pre-existing sync'ed block
+    private void setLastCheckinFailure(Throwable t)
+    {
+        assert ( Thread.holdsLock(this));
+
+        this.lastCheckinFailure = t;
+        this.lastResourceTestFailure = t;
+    }
+
+    public synchronized Throwable getLastCheckoutFailure()
+    { return lastCheckoutFailure; }
+
+    //must be called from a pre-existing sync'ed block
+    private void setLastCheckoutFailure(Throwable t)
+    {
+        assert ( Thread.holdsLock(this));
+
+        this.lastCheckoutFailure = t;
+        this.lastResourceTestFailure = t;
+    }
+
+    public synchronized Throwable getLastIdleCheckFailure()
+    { return lastIdleTestFailure; }
+
+    //must be called from a pre-existing sync'ed block
+    private void setLastIdleCheckFailure(Throwable t)
+    {
+        assert ( Thread.holdsLock(this));
+
+        this.lastIdleTestFailure = t;
+        this.lastResourceTestFailure = t;
+    }
+
+    public synchronized Throwable getLastResourceTestFailure()
+    { return lastResourceTestFailure; }
+
+    public synchronized Throwable getLastAcquisitionFailure()
+    { return lastAcquisitionFailiure; }
+
+    // ought not be called while holding this' lock
+    private synchronized void setLastAcquisitionFailure( Throwable t )
+    { this.lastAcquisitionFailiure = t; }
+
+    public synchronized int getNumCheckoutWaiters()
+    { return acquireWaiters.size(); }
+
+    private void addToFormerResources( Object resc )
+    { formerResources.put( resc, null ); }
+
+    private boolean isFormerResource( Object resc )
+    { return formerResources.keySet().contains( resc ); }
+
+    /**
+     * @param factory may be null
+     */
+    public BasicResourcePool(Manager                  mgr, 
+                    int                      start,
+                    int                      min, 
+                    int                      max, 
+                    int                      inc,
+                    int                      num_acq_attempts,
+                    int                      acq_attempt_delay,
+                    long                     check_idle_resources_delay,
+                    long                     max_resource_age,
+                    long                     max_idle_time,
+                    long                     excess_max_idle_time,
+                    long                     destroy_unreturned_resc_time,
+                    long                     expiration_enforcement_delay,
+                    boolean                  break_on_acquisition_failure,
+                    boolean                  debug_store_checkout_exceptions,
+                    AsynchronousRunner       taskRunner,
+                    RunnableQueue            asyncEventQueue,
+                    Timer                    cullAndIdleRefurbishTimer,
+                    BasicResourcePoolFactory factory)
+    throws ResourcePoolException
+    {
+        try
+        {
+            this.mgr                              = mgr;
+            this.start                            = start;
+            this.min                              = min;
+            this.max                              = max;
+            this.inc                              = inc;
+            this.num_acq_attempts                 = num_acq_attempts;
+            this.acq_attempt_delay                = acq_attempt_delay;
+            this.check_idle_resources_delay       = check_idle_resources_delay;
+            this.max_resource_age                 = max_resource_age;
+            this.max_idle_time                    = max_idle_time;
+            this.excess_max_idle_time             = excess_max_idle_time;
+            this.destroy_unreturned_resc_time     = destroy_unreturned_resc_time;
+            //this.expiration_enforcement_delay     = expiration_enforcement_delay; -- set up below
+            this.break_on_acquisition_failure     = break_on_acquisition_failure;
+            this.debug_store_checkout_exceptions  = (debug_store_checkout_exceptions && destroy_unreturned_resc_time > 0);
+            this.taskRunner                       = taskRunner;
+            this.asyncEventQueue                  = asyncEventQueue;
+            this.cullAndIdleRefurbishTimer        = cullAndIdleRefurbishTimer;
+            this.factory                          = factory;
+
+            this.pending_acquires = 0;
+            this.pending_removes  = 0;
+
+            this.target_pool_size = Math.max(start, min);
+
+            if (asyncEventQueue != null)
+                this.rpes = new ResourcePoolEventSupport(this);
+            else
+                this.rpes = null;
+
+            //start acquiring our initial resources
+            ensureStartResources();
+
+            if (mustEnforceExpiration())
+            {
+                if (expiration_enforcement_delay <= 0)
+                    this.expiration_enforcement_delay = automaticExpirationEnforcementDelay();
+                else
+                    this.expiration_enforcement_delay = expiration_enforcement_delay;
+
+                this.cullTask = new CullTask();
+                //System.err.println("minExpirationTime(): " + minExpirationTime());
+                //System.err.println("this.expiration_enforcement_delay: " + this.expiration_enforcement_delay);
+                cullAndIdleRefurbishTimer.schedule( cullTask, minExpirationTime(), this.expiration_enforcement_delay );
+            }
+            else
+                this.expiration_enforcement_delay = expiration_enforcement_delay;
+
+            //System.err.println("this.check_idle_resources_delay: " + this.check_idle_resources_delay);
+            if (check_idle_resources_delay > 0)
+            {
+                this.idleRefurbishTask = new CheckIdleResourcesTask();
+                cullAndIdleRefurbishTimer.schedule( idleRefurbishTask, 
+                                check_idle_resources_delay, 
+                                check_idle_resources_delay );
+            }
+
+            if ( logger.isLoggable( MLevel.FINER ) )
+                logger.finer( this + " config: [start -> " + this.start + "; min -> " + this.min + "; max -> " + this.max + "; inc -> " + this.inc +
+                                "; num_acq_attempts -> " + this.num_acq_attempts + "; acq_attempt_delay -> " + this.acq_attempt_delay +
+                                "; check_idle_resources_delay -> " + this.check_idle_resources_delay + "; mox_resource_age -> " + this.max_resource_age +
+                                "; max_idle_time -> " + this.max_idle_time + "; excess_max_idle_time -> " + this.excess_max_idle_time +
+                                "; destroy_unreturned_resc_time -> " + this.destroy_unreturned_resc_time +
+                                "; expiration_enforcement_delay -> " + this.expiration_enforcement_delay + 
+                                "; break_on_acquisition_failure -> " + this.break_on_acquisition_failure + 
+                                "; debug_store_checkout_exceptions -> " + this.debug_store_checkout_exceptions + 
+                "]");
+
+        }
+        catch (Exception e)
+        {
+//          if ( logger.isLoggable( MLevel.WARNING) )
+//          logger.log( MLevel.WARNING, "Could not create resource pool due to Exception!", e );
+
+            throw ResourcePoolUtils.convertThrowable( e ); 
+        }
+    }
+
+//  private boolean timerRequired()
+//  { return mustEnforceExpiration() || mustTestIdleResources(); }
+
+    // no need to sync
+    private boolean mustTestIdleResources()
+    { return check_idle_resources_delay > 0; }
+
+    // no need to sync
+    private boolean mustEnforceExpiration()
+    {
+        return 
+        max_resource_age > 0 ||
+        max_idle_time > 0 ||
+        excess_max_idle_time > 0 ||
+        destroy_unreturned_resc_time > 0;
+    }
+
+    // no need to sync
+    private long minExpirationTime()
+    {
+        long out = Long.MAX_VALUE;
+        if (max_resource_age > 0)
+            out = Math.min( out, max_resource_age );
+        if (max_idle_time > 0)
+            out = Math.min( out, max_idle_time );
+        if (excess_max_idle_time > 0)
+            out = Math.min( out, excess_max_idle_time );
+        if (destroy_unreturned_resc_time > 0)
+            out = Math.min( out, destroy_unreturned_resc_time );
+        return out;
+    }
+
+    private long automaticExpirationEnforcementDelay()
+    {
+        long out = minExpirationTime();
+        out /= AUTO_CULL_FREQUENCY_DIVISOR;
+        out = Math.min( out, AUTO_MAX_CULL_FREQUENCY );
+        out = Math.max( out, AUTO_MIN_CULL_FREQUENCY );
+        return out;
+    }
+
+    public long getEffectiveExpirationEnforcementDelay()
+    { return expiration_enforcement_delay; }
+
+    private synchronized boolean isBroken()
+    { return broken; }
+
+    // no need to sync
+    private boolean supportsEvents()
+    { return asyncEventQueue != null; }
+
+    public Object checkoutResource() 
+    throws ResourcePoolException, InterruptedException
+    {
+        try { return checkoutResource( 0 ); }
+        catch (TimeoutException e)
+        {
+            //this should never happen
+            //e.printStackTrace();
+            if ( logger.isLoggable( MLevel.WARNING ) )
+                logger.log( MLevel.WARNING, "Huh??? TimeoutException with no timeout set!!!", e);
+
+            throw new ResourcePoolException("Huh??? TimeoutException with no timeout set!!!", e);
+        }
+    }
+
+    // must be called from synchronized method, idempotent
+    private void _recheckResizePool()
+    {
+        assert Thread.holdsLock(this);
+
+        if (! broken)
+        {
+            int msz = managed.size();
+            //int expected_size = msz + pending_acquires - pending_removes;
+
+//          System.err.print("target: " + target_pool_size);
+//          System.err.println(" (msz: " + msz + "; pending_acquires: " + pending_acquires + "; pending_removes: " + pending_removes + ')');
+            //new Exception( "_recheckResizePool() STACK TRACE" ).printStackTrace();
+
+            int shrink_count;
+            int expand_count;
+
+            if ((shrink_count = msz - pending_removes - target_pool_size) > 0)
+                shrinkPool( shrink_count );
+            else if ((expand_count = target_pool_size - (msz + pending_acquires)) > 0)
+                expandPool( expand_count );
+        }
+    }
+
+    private synchronized void incrementPendingAcquires()
+    { 
+        ++pending_acquires; 
+
+        if (logger.isLoggable(MLevel.FINEST))
+            logger.finest("incremented pending_acquires: " + pending_acquires);
+        //new Exception("ACQUIRE SOURCE STACK TRACE").printStackTrace();
+    }
+
+    private synchronized void incrementPendingRemoves()
+    { 
+        ++pending_removes; 
+
+        if (logger.isLoggable(MLevel.FINEST))
+            logger.finest("incremented pending_removes: " + pending_removes);
+        //new Exception("REMOVE SOURCE STACK TRACE").printStackTrace();
+    }
+
+    private synchronized void decrementPendingAcquires()
+    { 
+        --pending_acquires; 
+
+        if (logger.isLoggable(MLevel.FINEST))
+            logger.finest("decremented pending_acquires: " + pending_acquires);
+        //new Exception("ACQUIRE SOURCE STACK TRACE").printStackTrace();
+    }
+
+    private synchronized void decrementPendingRemoves()
+    { 
+        --pending_removes; 
+
+        if (logger.isLoggable(MLevel.FINEST))
+            logger.finest("decremented pending_removes: " + pending_removes);
+        //new Exception("ACQUIRE SOURCE STACK TRACE").printStackTrace();
+    }
+
+    // idempotent
+    private synchronized void recheckResizePool()
+    { _recheckResizePool(); }
+
+    // must be called from synchronized method
+    private void expandPool(int count)
+    {
+        assert Thread.holdsLock(this);
+
+        // XXX: temporary switch -- assuming no problems appear, we'll get rid of AcquireTask
+        //      in favor of ScatteredAcquireTask
+        if ( USE_SCATTERED_ACQUIRE_TASK )
+        {
+            for (int i = 0; i < count; ++i)
+                taskRunner.postRunnable( new ScatteredAcquireTask() );
+        }
+        else
+        {
+            for (int i = 0; i < count; ++i)
+                taskRunner.postRunnable( new AcquireTask() );
+        }
+    }
+
+    // must be called from synchronized method
+    private void shrinkPool(int count)
+    {
+        assert Thread.holdsLock(this);
+
+        for (int i = 0; i < count; ++i)
+            taskRunner.postRunnable( new RemoveTask() ); 
+    }
+
+    /*
+     * This function recursively calls itself... under nonpathological
+     * situations, it shouldn't be a problem, but if resources can never
+     * successfully check out for some reason, we might blow the stack...
+     *
+     * by the semantics of wait(), a timeout of zero means forever.
+     */
+    public Object checkoutResource( long timeout )
+    throws TimeoutException, ResourcePoolException, InterruptedException
+    {
+        Object resc = prelimCheckoutResource( timeout );
+
+        boolean refurb = attemptRefurbishResourceOnCheckout( resc );
+
+        synchronized( this )
+        {
+            if (!refurb)
+            {
+                removeResource( resc );
+                ensureMinResources();
+                resc = null;
+            }
+            else
+            {
+                asyncFireResourceCheckedOut( resc, managed.size(), unused.size(), excluded.size() );
+                if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) trace();
+
+                PunchCard card = (PunchCard) managed.get( resc );
+                if (card == null) //the resource has been removed!
+                {
+                    if (logger.isLoggable( MLevel.FINE ))
+                        logger.fine("Resource " + resc + " was removed from the pool while it was being checked out " +
+                        " or refurbished for checkout.");
+                    resc = null;
+                }
+                else
+                {
+                    card.checkout_time = System.currentTimeMillis();
+                    if (debug_store_checkout_exceptions)
+                        card.checkoutStackTraceException = new Exception("DEBUG ONLY: Overdue resource check-out stack trace.");
+                }
+            }
+        }
+
+        // best to do the recheckout while we don't hold this'
+        // lock, so we don't refurbish-on-checkout while holding.
+        if (resc == null)
+            return checkoutResource( timeout );
+        else
+            return resc;
+    }
+
+    private synchronized Object prelimCheckoutResource( long timeout )
+    throws TimeoutException, ResourcePoolException, InterruptedException
+    {
+        try
+        {
+            ensureNotBroken();
+
+            int available = unused.size();
+            if (available == 0)
+            {
+                int msz = managed.size();
+
+                if (msz < max)
+                {
+                    // to cover all the load, we need the current size, plus those waiting already for acquisition, 
+                    // plus the current client 
+                    int desired_target = msz + acquireWaiters.size() + 1;
+
+                    if (logger.isLoggable(MLevel.FINER))
+                        logger.log(MLevel.FINER, "acquire test -- pool size: " + msz + "; target_pool_size: " + target_pool_size + "; desired target? " + desired_target);
+
+                    if (desired_target >= target_pool_size)
+                    {
+                        //make sure we don't grab less than inc Connections at a time, if we can help it.
+                        desired_target = Math.max(desired_target, target_pool_size + inc);
+
+                        //make sure our target is within its bounds
+                        target_pool_size = Math.max( Math.min( max, desired_target ), min );
+
+                        _recheckResizePool();
+                    }
+                }
+                else
+                {
+                    if (logger.isLoggable(MLevel.FINER))
+                        logger.log(MLevel.FINER, "acquire test -- pool is already maxed out. [managed: " + msz + "; max: " + max + "]");
+                }
+
+                awaitAvailable(timeout); //throws timeout exception
+            }
+
+            Object  resc = unused.get(0);
+
+            // this is a hack -- but "doing it right" adds a lot of complexity, and collisions between
+            // an idle check and a checkout should be relatively rare. anyway, it should work just fine.
+            if ( idleCheckResources.contains( resc ) )
+            {
+                if (Debug.DEBUG && logger.isLoggable( MLevel.FINER))
+                    logger.log( MLevel.FINER, 
+                                    "Resource we want to check out is in idleCheck! (waiting until idle-check completes.) [" + this + "]");
+
+                // we'll move remove() to after the if, so we don't have to add back
+                // unused.add(0, resc );
+
+                // we'll wait for "something to happen" -- probably an idle check to
+                // complete -- then we'll try again and hope for the best.
+                Thread t = Thread.currentThread();
+                try
+                {
+                    otherWaiters.add ( t );
+                    this.wait( timeout );
+                    ensureNotBroken();
+                }
+                finally
+                { otherWaiters.remove( t ); }
+                return prelimCheckoutResource( timeout );
+            }
+            else if ( shouldExpire( resc ) )
+            {
+                removeResource( resc );
+                ensureMinResources();
+                return prelimCheckoutResource( timeout );
+            }
+            else
+            {
+                unused.remove(0);
+                return resc;
+            }
+        }
+        catch ( ResourceClosedException e ) // one of our async threads died
+        {
+            //System.err.println(this + " -- the pool was found to be closed or broken during an attempt to check out a resource.");
+            //e.printStackTrace();
+            if (logger.isLoggable( MLevel.SEVERE ))
+                logger.log( MLevel.SEVERE, this + " -- the pool was found to be closed or broken during an attempt to check out a resource.", e );
+
+            this.unexpectedBreak();
+            throw e;
+        }
+        catch ( InterruptedException e )
+        {
+            // 		System.err.println(this + " -- an attempt to checkout a resource was interrupted: some other thread " +
+            // 				   "must have either interrupted the Thread attempting checkout, or close() was called on the pool.");
+            // 		e.printStackTrace();
+            if (broken)
+            {
+                if (logger.isLoggable( MLevel.FINER ))
+                    logger.log(MLevel.FINER, 
+                                    this + " -- an attempt to checkout a resource was interrupted, because the pool is now closed. " +
+                                    "[Thread: " + Thread.currentThread().getName() + ']',
+                                    e );
+                else if (logger.isLoggable( MLevel.INFO ))
+                    logger.log(MLevel.INFO, 
+                                    this + " -- an attempt to checkout a resource was interrupted, because the pool is now closed. " +
+                                    "[Thread: " + Thread.currentThread().getName() + ']');
+            }
+            else
+            {
+                if (logger.isLoggable( MLevel.WARNING ))
+                {
+                    logger.log(MLevel.WARNING, 
+                                    this + " -- an attempt to checkout a resource was interrupted, and the pool is still live: some other thread " +
+                                    "must have either interrupted the Thread attempting checkout!",
+                                    e );
+                }
+            }
+            throw e;
+        }
+    }
+
+    public synchronized void checkinResource( Object resc ) 
+    throws ResourcePoolException
+    {
+        try
+        {
+            //we permit straggling resources to be checked in 
+            //without exception even if we are broken
+            if (managed.keySet().contains(resc))
+                doCheckinManaged( resc );
+            else if (excluded.contains(resc))
+                doCheckinExcluded( resc );
+            else if ( isFormerResource(resc) )
+            {
+                if ( logger.isLoggable( MLevel.FINER ) )
+                    logger.finer("Resource " + resc + " checked-in after having been checked-in already, or checked-in after " +
+                    " having being destroyed for being checked-out too long.");
+            }
+            else
+                throw new ResourcePoolException("ResourcePool" + (broken ? " [BROKEN!]" : "") + ": Tried to check-in a foreign resource!");
+            if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) trace();
+        }
+        catch ( ResourceClosedException e ) // one of our async threads died
+        {
+//          System.err.println(this + 
+//          " - checkinResource( ... ) -- even broken pools should allow checkins without exception. probable resource pool bug.");
+//          e.printStackTrace();
+
+            if ( logger.isLoggable( MLevel.SEVERE ) )
+                logger.log( MLevel.SEVERE, 
+                                this + " - checkinResource( ... ) -- even broken pools should allow checkins without exception. probable resource pool bug.", 
+                                e);
+
+            this.unexpectedBreak();
+            throw e;
+        }
+    }
+
+    public synchronized void checkinAll()
+    throws ResourcePoolException
+    {
+        try
+        {
+            Set checkedOutNotExcluded = new HashSet( managed.keySet() );
+            checkedOutNotExcluded.removeAll( unused );
+            for (Iterator ii = checkedOutNotExcluded.iterator(); ii.hasNext(); )
+                doCheckinManaged( ii.next() );
+            for (Iterator ii = excluded.iterator(); ii.hasNext(); )
+                doCheckinExcluded( ii.next() );
+        }
+        catch ( ResourceClosedException e ) // one of our async threads died
+        {
+//          System.err.println(this + 
+//          " - checkinAll() -- even broken pools should allow checkins without exception. probable resource pool bug.");
+//          e.printStackTrace();
+
+            if ( logger.isLoggable( MLevel.SEVERE ) )
+                logger.log( MLevel.SEVERE,
+                                this + " - checkinAll() -- even broken pools should allow checkins without exception. probable resource pool bug.",
+                                e );
+
+            this.unexpectedBreak();
+            throw e;
+        }
+    }
+
+    public synchronized int statusInPool( Object resc )
+    throws ResourcePoolException
+    {
+        try
+        {
+            if ( unused.contains( resc ) )
+                return KNOWN_AND_AVAILABLE;
+            else if ( managed.keySet().contains( resc ) || excluded.contains( resc ) )
+                return KNOWN_AND_CHECKED_OUT;
+            else
+                return UNKNOWN_OR_PURGED;
+        }
+        catch ( ResourceClosedException e ) // one of our async threads died
+        {
+//          e.printStackTrace();
+            if ( logger.isLoggable( MLevel.SEVERE ) )
+                logger.log( MLevel.SEVERE, "Apparent pool break.", e );
+            this.unexpectedBreak();
+            throw e;
+        }
+    }
+
+    public synchronized void markBroken(Object resc) 
+    {
+        try
+        { 
+            if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ))
+                logger.log( MLevel.FINER, "Resource " + resc + " marked broken by pool (" + this + ").");
+
+            _markBroken( resc ); 
+            ensureMinResources();
+        }
+        catch ( ResourceClosedException e ) // one of our async threads died
+        {
+            //e.printStackTrace();
+            if ( logger.isLoggable( MLevel.SEVERE ) )
+                logger.log( MLevel.SEVERE, "Apparent pool break.", e );
+            this.unexpectedBreak();
+        }
+    }
+
+    //min is immutable, no need to synchronize
+    public int getMinPoolSize()
+    { return min; }
+
+    //max is immutable, no need to synchronize
+    public int getMaxPoolSize()
+    { return max; }
+
+    public synchronized int getPoolSize()
+    throws ResourcePoolException
+    { return managed.size(); }
+
+//  //i don't think i like the async, no-guarantees approach
+//  public synchronized void requestResize( int req_sz )
+//  {
+//  if (req_sz > max)
+//  req_sz = max;
+//  else if (req_sz < min)
+//  req_sz = min;
+//  int sz = managed.size();
+//  if (req_sz > sz)
+//  postAcquireUntil( req_sz );
+//  else if (req_sz < sz)
+//  postRemoveTowards( req_sz );
+//  }
+
+    public synchronized int getAvailableCount()
+    { return unused.size(); }
+
+    public synchronized int getExcludedCount()
+    { return excluded.size(); }
+
+    public synchronized int getAwaitingCheckinCount()
+    { return managed.size() - unused.size() + excluded.size(); }
+
+    public synchronized void resetPool()
+    {
+        try
+        {
+            for (Iterator ii = cloneOfManaged().keySet().iterator(); ii.hasNext();)
+                markBrokenNoEnsureMinResources(ii.next());
+            ensureMinResources();
+        }
+        catch ( ResourceClosedException e ) // one of our async threads died
+        {
+            //e.printStackTrace();
+            if ( logger.isLoggable( MLevel.SEVERE ) )
+                logger.log( MLevel.SEVERE, "Apparent pool break.", e );
+            this.unexpectedBreak();
+        }
+    }
+
+    public synchronized void close() 
+    throws ResourcePoolException
+    {
+        //we permit closes when we are already broken, so
+        //that resources that were checked out when the break
+        //occured can still be cleaned up
+        close( true );
+    }
+
+    public void finalize() throws Throwable
+    {
+        //obviously, clients mustn't rely on finalize,
+        //but must close pools ASAP after use.
+        //System.err.println("finalizing..." + this);
+
+        if (! broken )
+            this.close();
+    }
+
+    //no need to sync
+    public void addResourcePoolListener(ResourcePoolListener rpl)
+    { 
+        if ( ! supportsEvents() )
+            throw new RuntimeException(this + " does not support ResourcePoolEvents. " +
+            "Probably it was constructed by a BasicResourceFactory configured not to support such events.");
+        else
+            rpes.addResourcePoolListener(rpl); 
+    }
+
+    //no need to sync
+    public void removeResourcePoolListener(ResourcePoolListener rpl)
+    { 
+        if ( ! supportsEvents() )
+            throw new RuntimeException(this + " does not support ResourcePoolEvents. " +
+            "Probably it was constructed by a BasicResourceFactory configured not to support such events.");
+        else
+            rpes.removeResourcePoolListener(rpl); 
+    }
+
+    private synchronized boolean isForceKillAcquiresPending()
+    { return force_kill_acquires; }
+
+    // this is designed as a response to a determination that our resource source is down.
+    // rather than declaring ourselves broken in this case (as we did previously), we
+    // kill all pending acquisition attempts, but retry on new acqusition requests.
+    private synchronized void forceKillAcquires() throws InterruptedException
+    {
+        Thread t = Thread.currentThread();
+
+        try
+        {
+            force_kill_acquires = true;
+            this.notifyAll(); //wake up any threads waiting on an acquire, and force them all to die.
+            while (acquireWaiters.size() > 0) //we want to let all the waiting acquires die before we unset force_kill_acquires
+            {
+                otherWaiters.add( t ); 
+                this.wait();
+            }
+            force_kill_acquires = false;
+        }
+        finally
+        { otherWaiters.remove( t ); }
+    }
+
+    //same as close(), but we do not destroy checked out
+    //resources
+    private synchronized void unexpectedBreak()
+    {
+        if ( logger.isLoggable( MLevel.SEVERE ) )
+            logger.log( MLevel.SEVERE, this + " -- Unexpectedly broken!!!", new ResourcePoolException("Unexpected Break Stack Trace!") );
+        close( false );
+    }
+
+    // no need to sync
+    private boolean canFireEvents()
+    { return ( asyncEventQueue != null && !isBroken() ); }
+
+    // no need to sync
+    private void asyncFireResourceAcquired( final Object       resc,
+                    final int          pool_size,
+                    final int          available_size,
+                    final int          removed_but_unreturned_size )
+    {
+        if ( canFireEvents() )
+        {
+            Runnable r = new Runnable()
+            {
+                public void run()
+                {rpes.fireResourceAcquired(resc, pool_size, available_size, removed_but_unreturned_size);}
+            };
+            asyncEventQueue.postRunnable(r);
+        }
+    }
+
+    // no need to sync
+    private void asyncFireResourceCheckedIn( final Object       resc,
+                    final int          pool_size,
+                    final int          available_size,
+                    final int          removed_but_unreturned_size )
+    {
+        if ( canFireEvents() )
+        {
+            Runnable r = new Runnable()
+            {
+                public void run()
+                {rpes.fireResourceCheckedIn(resc, pool_size, available_size, removed_but_unreturned_size);}
+            };
+            asyncEventQueue.postRunnable(r);
+        }
+    }
+
+    // no need to sync
+    private void asyncFireResourceCheckedOut( final Object       resc,
+                    final int          pool_size,
+                    final int          available_size,
+                    final int          removed_but_unreturned_size )
+    {
+        if ( canFireEvents() )
+        {
+            Runnable r = new Runnable()
+            {
+                public void run()
+                {rpes.fireResourceCheckedOut(resc,pool_size,available_size,removed_but_unreturned_size);}
+            };
+            asyncEventQueue.postRunnable(r);
+        }
+    }
+
+    // no need to sync
+    private void asyncFireResourceRemoved( final Object       resc,
+                    final boolean      checked_out_resource,
+                    final int          pool_size,
+                    final int          available_size,
+                    final int          removed_but_unreturned_size )
+    {
+        if ( canFireEvents() )
+        {
+            //System.err.println("ASYNC RSRC REMOVED");
+            //new Exception().printStackTrace();
+            Runnable r = new Runnable()
+            {
+                public void run()
+                {
+                    rpes.fireResourceRemoved(resc, checked_out_resource,
+                                    pool_size,available_size,removed_but_unreturned_size);
+                }
+            };
+            asyncEventQueue.postRunnable(r);
+        }
+    }
+
+    // needn't be called from a sync'ed method
+    private void destroyResource(final Object resc)
+    { destroyResource( resc, false ); }
+
+    // needn't be called from a sync'ed method
+    private void destroyResource(final Object resc, boolean synchronous)
+    {
+        class DestroyResourceTask implements Runnable
+        {
+            public void run()
+            {
+                try 
+                { 
+                    if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ))
+                        logger.log(MLevel.FINER, "Preparing to destroy resource: " + resc);
+
+                    mgr.destroyResource(resc); 
+
+                    if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX && logger.isLoggable( MLevel.FINER ))
+                        logger.log(MLevel.FINER, "Successfully destroyed resource: " + resc);
+                }
+                catch ( Exception e )
+                {
+                    if ( logger.isLoggable( MLevel.WARNING ) )
+                        logger.log( MLevel.WARNING, "Failed to destroy resource: " + resc, e );
+
+                    // System.err.println("Failed to destroy resource: " + resc);
+                    // e.printStackTrace();
+                }
+            }
+        }
+
+        Runnable r = new DestroyResourceTask();
+        if ( synchronous || broken ) //if we're broken, our taskRunner may be dead, so we destroy synchronously
+        {
+            if ( logger.isLoggable(MLevel.FINEST) && !broken && Boolean.TRUE.equals( ThreadUtils.reflectiveHoldsLock( this ) ) )
+                logger.log( MLevel.FINEST, 
+                                this + ": Destroyiong a resource on an active pool, synchronousy while holding pool's lock! " +
+                                "(not a bug, but a potential bottleneck... is there a good reason for this?)", 
+                                new Exception("DEBUG STACK TRACE") );
+
+            r.run();
+        }
+        else
+        {
+            try { taskRunner.postRunnable( r ); }
+            catch (Exception e)
+            {
+                if (logger.isLoggable(MLevel.FINER))
+                    logger.log( MLevel.FINER, 
+                                    "AsynchronousRunner refused to accept task to destroy resource. " +
+                                    "It is probably shared, and has probably been closed underneath us. " +
+                                    "Reverting to synchronous destruction. This is not usually a problem.",
+                                    e );
+                destroyResource( resc, true );
+            }
+        }
+    }
+
+
+    //this method SHOULD NOT be invoked from a synchronized
+    //block!!!!
+    private void doAcquire() throws Exception
+    {
+        assert !Thread.holdsLock( this );
+
+        Object resc = mgr.acquireResource(); //note we acquire the resource while we DO NOT hold the pool's lock!
+
+        boolean destroy = false;
+        int msz;
+
+        synchronized(this) //assimilate resc if we do need it
+        {
+//          ++total_acquired;
+
+//          if (logger.isLoggable( MLevel.FINER))
+//          logger.log(MLevel.FINER, "acquired new resource, total_acquired: " + total_acquired);
+
+            msz = managed.size();
+            if (msz < target_pool_size)
+                assimilateResource(resc); 
+            else
+                destroy = true;
+        }
+
+        if (destroy)
+        {
+            mgr.destroyResource( resc ); //destroy resc if superfluous, without holding the pool's lock
+            if (logger.isLoggable( MLevel.FINER))
+                logger.log(MLevel.FINER, "destroying overacquired resource: " + resc);
+        }
+
+    }
+
+    public synchronized void setPoolSize( int sz ) throws ResourcePoolException
+    {
+        try
+        {
+            setTargetPoolSize( sz );
+            while ( managed.size() != sz )
+                this.wait();
+        }
+        catch (Exception e)
+        {
+            String msg = "An exception occurred while trying to set the pool size!";
+            if ( logger.isLoggable( MLevel.FINER ) )
+                logger.log( MLevel.FINER, msg, e );
+            throw ResourcePoolUtils.convertThrowable( msg, e );
+        }
+    }
+
+    public synchronized void setTargetPoolSize(int sz)
+    {
+        if (sz > max)
+        {
+            throw new IllegalArgumentException("Requested size [" + sz + 
+                            "] is greater than max [" + max +
+            "].");
+        } 
+        else if (sz < min)
+        {
+            throw new IllegalArgumentException("Requested size [" + sz + 
+                            "] is less than min [" + min +
+            "].");
+        }
+
+        this.target_pool_size = sz;
+
+        _recheckResizePool();
+    }
+
+
+//  private void acquireUntil(int num) throws Exception
+//  {
+//  int msz = managed.size();
+//  for (int i = msz; i < num; ++i)
+//  assimilateResource();
+//  }
+
+    //the following methods should only be invoked from 
+    //sync'ed methods / blocks...
+
+//  private Object useUnusedButNotInIdleCheck()
+//  {
+//  for (Iterator ii = unused.iterator(); ii.hasNext(); )
+//  {
+//  Object maybeOut = ii.next();
+//  if (! idleCheckResources.contains( maybeOut ))
+//  {
+//  ii.remove();
+//  return maybeOut;
+//  }
+//  }
+//  throw new RuntimeException("Internal Error -- the pool determined that it did have a resource available for checkout, but was unable to find one.");
+//  }
+
+//  private int actuallyAvailable()
+//  { return unused.size() - idleCheckResources.size(); }
+
+    // must own this' lock
+    private void markBrokenNoEnsureMinResources(Object resc) 
+    {
+        assert Thread.holdsLock( this );
+
+        try
+        { 
+            _markBroken( resc ); 
+        }
+        catch ( ResourceClosedException e ) // one of our async threads died
+        {
+            //e.printStackTrace();
+            if ( logger.isLoggable( MLevel.SEVERE ) )
+                logger.log( MLevel.SEVERE, "Apparent pool break.", e );
+            this.unexpectedBreak();
+        }
+    }
+
+    // must own this' lock
+    private void _markBroken( Object resc )
+    {
+        assert Thread.holdsLock( this );
+
+        if ( unused.contains( resc ) )
+            removeResource( resc ); 
+        else
+            excludeResource( resc );
+    }
+
+    //DEBUG
+    //Exception firstClose = null;
+
+    public synchronized void close( boolean close_checked_out_resources )
+    {
+        if (! broken ) //ignore repeated calls to close
+        {
+            //DEBUG
+            //firstClose = new Exception("First close() -- debug stack trace [CRAIG]");
+            //firstClose.printStackTrace();
+
+            this.broken = true;
+            final Collection cleanupResources = ( close_checked_out_resources ? (Collection) cloneOfManaged().keySet() : (Collection) cloneOfUnused() );
+            if ( cullTask != null )
+                cullTask.cancel();
+            if (idleRefurbishTask != null)
+                idleRefurbishTask.cancel();
+
+            // we destroy resources asynchronously, but with a dedicated one-off Thread, rather than
+            // our asynchronous runner, because our asynchrous runner may be shutting down. The
+            // destruction is asynchrounous because destroying a resource might require the resource's
+            // lock, and we already have the pool's lock. But client threads may well have the resource's
+            // lock while they try to check-in to the pool. The async destruction of resources avoids
+            // the possibility of deadlock.
+
+            managed.keySet().removeAll( cleanupResources );
+            unused.removeAll( cleanupResources );
+            Thread resourceDestroyer = new Thread("Resource Destroyer in BasicResourcePool.close()")
+            {
+                public void run()
+                {
+                    for (Iterator ii = cleanupResources.iterator(); ii.hasNext();)
+                    {
+                        try
+                        {
+                            Object resc = ii.next();
+                            //System.err.println("Destroying resource... " + resc);
+
+                            destroyResource( resc, true );
+                        }
+                        catch (Exception e)
+                        {
+                            if (Debug.DEBUG) 
+                            {
+                                //e.printStackTrace();
+                                if ( logger.isLoggable( MLevel.FINE ) )
+                                    logger.log( MLevel.FINE, "BasicResourcePool -- A resource couldn't be cleaned up on close()", e );
+                            }
+                        }
+                    }
+                }
+            };
+            resourceDestroyer.start();
+
+            for (Iterator ii = acquireWaiters.iterator(); ii.hasNext(); )
+                ((Thread) ii.next()).interrupt();
+            for (Iterator ii = otherWaiters.iterator(); ii.hasNext(); )
+                ((Thread) ii.next()).interrupt();
+            if (factory != null)
+                factory.markBroken( this );
+
+            // System.err.println(this + " closed.");
+        }
+        else
+        {
+            if ( logger.isLoggable( MLevel.WARNING ) )
+                logger.warning(this + " -- close() called multiple times.");
+            //System.err.println(this + " -- close() called multiple times.");
+
+            //DEBUG
+            //firstClose.printStackTrace();
+            //new Exception("Repeat close() [CRAIG]").printStackTrace();
+        }
+    }
+
+    private void doCheckinManaged( final Object resc ) throws ResourcePoolException
+    {
+        assert Thread.holdsLock( this );
+
+        if (unused.contains(resc))
+        {
+            if ( Debug.DEBUG )
+                throw new ResourcePoolException("Tried to check-in an already checked-in resource: " + resc);
+        }
+        else if (broken)
+            removeResource( resc, true ); //synchronous... if we're broken, async tasks might not work
+        else
+        {
+            class RefurbishCheckinResourceTask implements Runnable
+            {
+                public void run()
+                {
+                    boolean resc_okay = attemptRefurbishResourceOnCheckin( resc );
+                    synchronized( BasicResourcePool.this )
+                    {
+                        PunchCard card = (PunchCard) managed.get( resc );
+
+                        if ( resc_okay && card != null) //we have to check that the resource is still in the pool
+                        {
+                            unused.add(0,  resc );
+
+                            card.last_checkin_time = System.currentTimeMillis();
+                            card.checkout_time = -1;
+                        }
+                        else
+                        {
+                            if (card != null)
+                                card.checkout_time = -1; //so we don't see this as still checked out and log an overdue cxn in removeResource()
+
+                            removeResource( resc );
+                            ensureMinResources();
+
+                            if (card == null && logger.isLoggable( MLevel.FINE ))
+                                logger.fine("Resource " + resc + " was removed from the pool during its refurbishment for checkin.");
+                        }
+
+                        asyncFireResourceCheckedIn( resc, managed.size(), unused.size(), excluded.size() );
+                        BasicResourcePool.this.notifyAll();
+                    }
+                }
+            }
+
+            Runnable doMe = new RefurbishCheckinResourceTask();
+            taskRunner.postRunnable( doMe );
+        }
+    }
+
+    private void doCheckinExcluded( Object resc )
+    {
+        assert Thread.holdsLock( this );
+
+        excluded.remove(resc);
+        destroyResource(resc);
+    }
+
+    /*
+     * by the semantics of wait(), a timeout of zero means forever.
+     */
+    private void awaitAvailable(long timeout) throws InterruptedException, TimeoutException, ResourcePoolException
+    {
+        assert Thread.holdsLock( this );
+
+        if (force_kill_acquires)
+            throw new ResourcePoolException("A ResourcePool cannot acquire a new resource -- the factory or source appears to be down.");
+
+        Thread t = Thread.currentThread();
+        try
+        {
+            acquireWaiters.add( t );
+
+            int avail;
+            long start = ( timeout > 0 ? System.currentTimeMillis() : -1);
+            if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX)
+            {
+                if ( logger.isLoggable( MLevel.FINE ) )
+                    logger.fine("awaitAvailable(): " + 
+                                    (exampleResource != null ? 
+                                                    exampleResource : 
+                                    "[unknown]") );
+                trace();
+            }
+            while ((avail = unused.size()) == 0) 
+            {
+                // the if case below can only occur when 1) a user attempts a
+                // checkout which would provoke an acquire; 2) this
+                // increments the pending acquires, so we go to the
+                // wait below without provoking postAcquireMore(); 3)
+                // the resources are acquired; 4) external management
+                // of the pool (via for instance unpoolResource() 
+                // depletes the newly acquired resources before we
+                // regain this' monitor; 5) we fall into wait() with
+                // no acquires being scheduled, and perhaps a managed.size()
+                // of zero, leading to deadlock. This could only occur in
+                // fairly pathological situations where the pool is being
+                // externally forced to a very low (even zero) size, but 
+                // since I've seen it, I've fixed it.
+                if (pending_acquires == 0 && managed.size() < max)
+                    _recheckResizePool();
+
+                this.wait(timeout);
+                if (timeout > 0 && System.currentTimeMillis() - start > timeout)
+                    throw new TimeoutException("A client timed out while waiting to acquire a resource from " + this + " -- timeout at awaitAvailable()");
+                if (force_kill_acquires)
+                    throw new CannotAcquireResourceException("A ResourcePool could not acquire a resource from its primary factory or source.");
+                ensureNotBroken();
+            }
+        }
+        finally
+        {
+            acquireWaiters.remove( t );
+            if (acquireWaiters.size() == 0)
+                this.notifyAll();
+        }
+    }
+
+    private void assimilateResource( Object resc ) throws Exception
+    {
+        assert Thread.holdsLock( this );
+
+        managed.put(resc, new PunchCard());
+        unused.add(0, resc);
+        //System.err.println("assimilate resource... unused: " + unused.size());
+        asyncFireResourceAcquired( resc, managed.size(), unused.size(), excluded.size() );
+        this.notifyAll();
+        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) trace();
+        if (Debug.DEBUG && exampleResource == null)
+            exampleResource = resc;
+    }
+
+    // should NOT be called from synchronized method
+    private void synchronousRemoveArbitraryResource()
+    { 
+        assert !Thread.holdsLock( this );
+
+        Object removeMe = null;
+
+        synchronized ( this )
+        {
+            if (unused.size() > 0)
+            {
+                removeMe = unused.get(0);
+                managed.remove(removeMe);
+                unused.remove(removeMe);
+            }
+            else
+            {
+                Set checkedOut = cloneOfManaged().keySet();
+                if ( checkedOut.isEmpty() )
+                {
+                    unexpectedBreak();
+                    logger.severe("A pool from which a resource is requested to be removed appears to have no managed resources?!");
+                }
+                else
+                    excludeResource( checkedOut.iterator().next() );
+            }
+        }
+
+        if (removeMe != null)
+            destroyResource( removeMe, true );
+    }
+
+    private void removeResource(Object resc)
+    { removeResource( resc, false ); }
+
+    private void removeResource(Object resc, boolean synchronous)
+    {
+        assert Thread.holdsLock( this );
+
+        PunchCard pc = (PunchCard) managed.remove(resc);
+
+        if (pc != null)
+        {
+            if ( pc.checkout_time > 0 && !broken) //this is a checked-out resource in an active pool, must be overdue if we are removing it
+            {
+                if (logger.isLoggable( MLevel.INFO ) )
+                {
+                    logger.info("A checked-out resource is overdue, and will be destroyed: " + resc);
+                    if (pc.checkoutStackTraceException != null)
+                    {
+                        logger.log( MLevel.INFO,
+                                        "Logging the stack trace by which the overdue resource was checked-out.",
+                                        pc.checkoutStackTraceException );
+                    }
+                }
+            }
+        }
+        else if ( logger.isLoggable( MLevel.FINE ) )
+            logger.fine("Resource " + resc + " was removed twice. (Lotsa reasons a resource can be removed, sometimes simultaneously. It's okay)");
+
+        unused.remove(resc);
+        destroyResource(resc, synchronous);
+        addToFormerResources( resc );
+        asyncFireResourceRemoved( resc, false, managed.size(), unused.size(), excluded.size() );
+
+        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) trace();
+        //System.err.println("RESOURCE REMOVED!");
+    }
+
+    //when we want to conceptually remove a checked
+    //out resource from the pool
+    private void excludeResource(Object resc)
+    {
+        assert Thread.holdsLock( this );
+
+        managed.remove(resc);
+        excluded.add(resc);
+        if (Debug.DEBUG && unused.contains(resc) )
+            throw new InternalError( "We should only \"exclude\" checked-out resources!" );
+        asyncFireResourceRemoved( resc, true, managed.size(), unused.size(), excluded.size() );
+    }
+
+    private void removeTowards( int new_sz )
+    {
+        assert Thread.holdsLock( this );
+
+        int num_to_remove = managed.size() - new_sz;
+        int count = 0;
+        for (Iterator ii = cloneOfUnused().iterator(); 
+        ii.hasNext() && count < num_to_remove; 
+        ++count)
+        {
+            Object resc = ii.next();
+            removeResource( resc );
+        }
+    }
+
+    private void cullExpired()
+    {
+        assert Thread.holdsLock( this );
+
+        if ( logger.isLoggable( MLevel.FINER ) )
+            logger.log( MLevel.FINER, "BEGIN check for expired resources.  [" + this + "]");
+
+        // if we do not time-out checkedout resources, we only need to test unused resources
+        Collection checkMe = ( destroy_unreturned_resc_time > 0 ? (Collection) cloneOfManaged().keySet() : cloneOfUnused() );
+
+        for ( Iterator ii = checkMe.iterator(); ii.hasNext(); )
+        {
+            Object resc = ii.next();
+            if ( shouldExpire( resc ) )
+            {
+                if ( logger.isLoggable( MLevel.FINER ) )
+                    logger.log( MLevel.FINER, "Removing expired resource: " + resc + " [" + this + "]");
+
+                target_pool_size = Math.max( min, target_pool_size - 1 ); //expiring a resource resources the target size to match
+
+                removeResource( resc );
+
+                if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) trace();
+            }
+        }
+        if ( logger.isLoggable( MLevel.FINER ) )
+            logger.log( MLevel.FINER, "FINISHED check for expired resources.  [" + this + "]");
+        ensureMinResources();
+    }
+
+    private void checkIdleResources()
+    {
+        assert Thread.holdsLock( this );
+
+        List u = cloneOfUnused();
+        for ( Iterator ii = u.iterator(); ii.hasNext(); )
+        {
+            Object resc = ii.next();
+            if ( idleCheckResources.add( resc ) )
+                taskRunner.postRunnable( new AsyncTestIdleResourceTask( resc ) );
+        }
+
+        if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) trace();
+    }
+
+    private boolean shouldExpire( Object resc )
+    {
+        assert Thread.holdsLock( this );
+
+        boolean expired = false;
+
+        PunchCard pc = (PunchCard) managed.get( resc );
+
+        // the resource has already been removed
+        // we return true, because removing twice does no harm
+        // (false should work as well, but true seems safer.
+        //  we certainly don't want to do anything else with
+        //  this resource.)
+        if (pc == null) 
+        {
+            if ( logger.isLoggable( MLevel.FINE ) )
+                logger.fine( "Resource " + resc + " was being tested for expiration, but has already been removed from the pool.");
+            return true;
+        }
+
+        long now = System.currentTimeMillis();
+
+        if (pc.checkout_time < 0) //resource is not checked out
+        {
+            long idle_age = now - pc.last_checkin_time;
+            if (excess_max_idle_time > 0)
+            {
+                int msz = managed.size();
+                expired = (msz > min && idle_age > excess_max_idle_time);
+                if ( expired && logger.isLoggable( MLevel.FINER ) )
+                    logger.log(MLevel.FINER, 
+                                    "EXPIRED excess idle resource: " + resc + 
+                                    " ---> idle_time: " + idle_age + 
+                                    "; excess_max_idle_time: " + excess_max_idle_time +
+                                    "; pool_size: " + msz +
+                                    "; min_pool_size: " + min +
+                                    " [" + this + "]");
+            }
+            if (!expired && max_idle_time > 0)
+            {
+                expired = idle_age > max_idle_time;
+                if ( expired && logger.isLoggable( MLevel.FINER ) )
+                    logger.log(MLevel.FINER, 
+                                    "EXPIRED idle resource: " + resc + 
+                                    " ---> idle_time: " + idle_age + 
+                                    "; max_idle_time: " + max_idle_time +
+                                    " [" + this + "]");
+            }
+            if (!expired && max_resource_age > 0)
+            {
+                long abs_age = now - pc.acquisition_time;
+                expired = ( abs_age > max_resource_age );
+
+                if ( expired && logger.isLoggable( MLevel.FINER ) )
+                    logger.log(MLevel.FINER, 
+                                    "EXPIRED old resource: " + resc + 
+                                    " ---> absolute_age: " + abs_age + 
+                                    "; max_absolute_age: " + max_resource_age +
+                                    " [" + this + "]");
+            }
+        }
+        else //resource is checked out
+        {
+            long checkout_age = now - pc.checkout_time;
+            expired = checkout_age > destroy_unreturned_resc_time;
+        }
+
+        return expired; 
+    }
+
+
+//  private boolean resourcesInIdleCheck()
+//  { return idleCheckresources.size() > 0; }
+
+//  private int countAvailable()
+//  { return unused.size() - idleCheckResources.size(); }
+
+
+    // we needn't hold this' lock
+    private void ensureStartResources()
+    { recheckResizePool(); }
+
+    // we needn't hold this' lock
+    private void ensureMinResources()
+    { recheckResizePool(); }
+
+    private boolean attemptRefurbishResourceOnCheckout( Object resc )
+    {
+        assert !Thread.holdsLock( this );
+
+        try
+        { 
+            mgr.refurbishResourceOnCheckout(resc); 
+            return true;
+        }
+        catch (Exception e)
+        {
+            //uh oh... bad resource...
+            if (Debug.DEBUG) 
+            {
+                //e.printStackTrace();
+                if (logger.isLoggable( MLevel.FINE ))
+                    logger.log( MLevel.FINE, "A resource could not be refurbished for checkout. [" + resc + ']', e );
+            }
+            synchronized (this)
+            {
+                ++failed_checkouts;
+                setLastCheckoutFailure(e);
+            }
+            return false;
+        }
+    }
+
+    private boolean attemptRefurbishResourceOnCheckin( Object resc )
+    {
+        assert !Thread.holdsLock( this );
+
+        try
+        { 
+            mgr.refurbishResourceOnCheckin(resc); 
+            return true;
+        }
+        catch (Exception e)
+        {
+            //uh oh... bad resource...
+            if (Debug.DEBUG) 
+            {
+                //e.printStackTrace();
+                if (logger.isLoggable( MLevel.FINE ))
+                    logger.log( MLevel.FINE, "A resource could not be refurbished on checkin. [" + resc + ']', e );
+            }
+            synchronized (this)
+            {
+                ++failed_checkins;
+                setLastCheckinFailure(e);
+            }
+            return false;
+        }
+    }
+
+    private void ensureNotBroken() throws ResourcePoolException
+    {
+        assert Thread.holdsLock( this );
+
+        if (broken) 
+            throw new ResourcePoolException("Attempted to use a closed or broken resource pool");
+    }
+
+    private void trace()
+    {
+        assert Thread.holdsLock( this );
+
+        if ( logger.isLoggable( MLevel.FINEST ) )
+        {
+            String exampleResStr = ( exampleResource == null ?
+                            "" :
+                                " (e.g. " + exampleResource +")");
+            logger.finest("trace " + this + " [managed: " + managed.size() + ", " +
+                            "unused: " + unused.size() + ", excluded: " +
+                            excluded.size() + ']' + exampleResStr );
+        }
+    }
+
+    private final HashMap cloneOfManaged()
+    { 
+        assert Thread.holdsLock( this );
+
+        return (HashMap) managed.clone(); 
+    }
+
+    private final LinkedList cloneOfUnused()
+    { 
+        assert Thread.holdsLock( this );
+
+        return (LinkedList) unused.clone(); 
+    }
+
+    private final HashSet cloneOfExcluded()
+    { 
+        assert Thread.holdsLock( this );
+
+        return (HashSet) excluded.clone(); 
+    }
+
+    class ScatteredAcquireTask implements Runnable
+    {
+        int attempts_remaining;
+
+        ScatteredAcquireTask()
+        { this ( (num_acq_attempts >= 0 ? num_acq_attempts : -1) , true ); }
+
+        private ScatteredAcquireTask(int attempts_remaining, boolean first_attempt)
+        { 
+            this.attempts_remaining = attempts_remaining; 
+            if (first_attempt)
+            {
+                incrementPendingAcquires();
+                if (logger.isLoggable(MLevel.FINEST))
+                    logger.finest("Starting acquisition series. Incremented pending_acquires [" + pending_acquires + "], " +
+                                    " attempts_remaining: " + attempts_remaining);
+            }
+            else
+            {
+                if (logger.isLoggable(MLevel.FINEST))
+                    logger.finest("Continuing acquisition series. pending_acquires [" + pending_acquires + "], " +
+                                    " attempts_remaining: " + attempts_remaining);
+            }
+        }
+
+        public void run()
+        {
+            try
+            {
+                boolean fkap = isForceKillAcquiresPending();
+                if (! fkap)
+                {
+                    //we don't want this call to be sync'd
+                    //on the pool, so that resource acquisition
+                    //does not interfere with other pool clients.
+                    BasicResourcePool.this.doAcquire();
+                }
+                decrementPendingAcquires();
+                if (logger.isLoggable(MLevel.FINEST))
+                    logger.finest("Acquisition series terminated " +
+                                    (fkap ? "because force-kill-acquires is pending" : "successfully") +
+                                    ". Decremented pending_acquires [" + pending_acquires + "], " +
+                                    " attempts_remaining: " + attempts_remaining);
+            }
+            catch (Exception e)
+            {
+                BasicResourcePool.this.setLastAcquisitionFailure(e);
+
+                if (attempts_remaining == 0) //last try in a round...
+                {
+                    decrementPendingAcquires();
+                    if ( logger.isLoggable( MLevel.WARNING ) )
+                    {
+                        logger.log( MLevel.WARNING,
+                                        this + " -- Acquisition Attempt Failed!!! Clearing pending acquires. " +
+                                        "While trying to acquire a needed new resource, we failed " +
+                                        "to succeed more than the maximum number of allowed " +
+                                        "acquisition attempts (" + num_acq_attempts + "). " + 
+                                        "Last acquisition attempt exception: ",
+                                        e);
+                    }
+                    if (break_on_acquisition_failure)
+                    {
+                        //System.err.println("\tTHE RESOURCE POOL IS PERMANENTLY BROKEN!");
+                        if ( logger.isLoggable( MLevel.SEVERE ) )
+                            logger.severe("A RESOURCE POOL IS PERMANENTLY BROKEN! [" + this + "] " +
+                                            "(because a series of " + num_acq_attempts + " acquisition attempts " +
+                            "failed.)");
+                        unexpectedBreak();
+                    }
+                    else
+                    {
+                        try { forceKillAcquires(); }
+                        catch (InterruptedException ie)
+                        {
+                            if ( logger.isLoggable(MLevel.WARNING) )
+                                logger.log(MLevel.WARNING, 
+                                                "Failed to force-kill pending acquisition attempts after acquisition failue, " +
+                                                " due to an InterruptedException!",
+                                                ie );
+
+                            // we might still have clients waiting, so we should try
+                            // to ensure there are sufficient connections to serve
+                            recheckResizePool();
+                        }
+                    }
+                    if (logger.isLoggable(MLevel.FINEST))
+                        logger.finest("Acquisition series terminated unsuccessfully. Decremented pending_acquires [" + pending_acquires + "], " +
+                                        " attempts_remaining: " + attempts_remaining);
+                }
+                else
+                {
+                    // if attempts_remaining < 0, we try to acquire forever, so the end-of-batch
+                    // log message below will never be triggered if there is a persistent problem
+                    // so in this case, it's better flag a higher-than-debug-level message for
+                    // each failed attempt. (Thanks to Eric Crahen for calling attention to this
+                    // issue.)
+                    MLevel logLevel = (attempts_remaining > 0 ? MLevel.FINE : MLevel.INFO);
+                    if (logger.isLoggable( logLevel ))
+                        logger.log( logLevel, "An exception occurred while acquiring a poolable resource. Will retry.", e );
+
+                    TimerTask doNextAcquire = new TimerTask()
+                    {
+                        public void run()
+                        { taskRunner.postRunnable( new ScatteredAcquireTask( attempts_remaining - 1, false ) ); }
+                    };
+                    cullAndIdleRefurbishTimer.schedule( doNextAcquire, acq_attempt_delay );
+                }
+            }
+        }
+
+    }
+
+    /*
+     *  task we post to separate thread to acquire
+     *  pooled resources
+     */
+    class AcquireTask implements Runnable
+    {
+        boolean success = false;
+
+        public AcquireTask() 
+        { incrementPendingAcquires(); }
+
+        public void run()
+        {
+            try
+            {
+                Exception lastException = null;
+                for (int i = 0; shouldTry( i ); ++i)
+                {
+                    try
+                    {
+                        if (i > 0)
+                            Thread.sleep(acq_attempt_delay); 
+
+                        //we don't want this call to be sync'd
+                        //on the pool, so that resource acquisition
+                        //does not interfere with other pool clients.
+                        BasicResourcePool.this.doAcquire();
+
+                        success = true;
+                    }
+                    catch (InterruptedException e)
+                    {
+                        // end the whole task on interrupt, regardless of success
+                        // or failure
+                        throw e;
+                    }
+                    catch (Exception e)
+                    {
+                        //e.printStackTrace();
+
+                        // if num_acq_attempts <= 0, we try to acquire forever, so the end-of-batch
+                        // log message below will never be triggered if there is a persistent problem
+                        // so in this case, it's better flag a higher-than-debug-level message for
+                        // each failed attempt. (Thanks to Eric Crahen for calling attention to this
+                        // issue.)
+                        MLevel logLevel = (num_acq_attempts > 0 ? MLevel.FINE : MLevel.INFO);
+                        if (logger.isLoggable( logLevel ))
+                            logger.log( logLevel, "An exception occurred while acquiring a poolable resource. Will retry.", e );
+
+                        lastException = e;
+                        setLastAcquisitionFailure(e);
+                    }
+                }
+                if (!success) 
+                {
+                    if ( logger.isLoggable( MLevel.WARNING ) )
+                    {
+                        logger.log( MLevel.WARNING,
+                                        this + " -- Acquisition Attempt Failed!!! Clearing pending acquires. " +
+                                        "While trying to acquire a needed new resource, we failed " +
+                                        "to succeed more than the maximum number of allowed " +
+                                        "acquisition attempts (" + num_acq_attempts + "). " + 
+                                        (lastException == null ? "" : "Last acquisition attempt exception: "),
+                                        lastException);
+                    }
+                    if (break_on_acquisition_failure)
+                    {
+                        //System.err.println("\tTHE RESOURCE POOL IS PERMANENTLY BROKEN!");
+                        if ( logger.isLoggable( MLevel.SEVERE ) )
+                            logger.severe("A RESOURCE POOL IS PERMANENTLY BROKEN! [" + this + "]");
+                        unexpectedBreak();
+                    }
+                    else
+                        forceKillAcquires();
+                }
+                else
+                    recheckResizePool();
+            }
+            catch ( ResourceClosedException e ) // one of our async threads died
+            {
+                //e.printStackTrace();
+                if ( Debug.DEBUG )
+                {
+                    if ( logger.isLoggable( MLevel.FINE ) )
+                        logger.log( MLevel.FINE, "a resource pool async thread died.", e );
+                }
+                unexpectedBreak();
+            }
+            catch (InterruptedException e) //from force kill acquires, or by the thread pool during the long task...
+            {
+                if ( logger.isLoggable( MLevel.WARNING ) )
+                {
+                    logger.log( MLevel.WARNING,
+                                    BasicResourcePool.this + " -- Thread unexpectedly interrupted while performing an acquisition attempt.",
+                                    e );
+                }
+
+//              System.err.println(BasicResourcePool.this + " -- Thread unexpectedly interrupted while waiting for stale acquisition attempts to die.");
+//              e.printStackTrace();
+
+                recheckResizePool();
+            }
+            finally
+            { decrementPendingAcquires(); }
+        }
+
+        private boolean shouldTry(int attempt_num)
+        {
+            //try if we haven't already succeeded
+            //and someone hasn't signalled that our resource source is down
+            //and not max attempts is set,
+            //or we are less than the set limit
+            return 
+            !success && 
+            !isForceKillAcquiresPending() &&
+            (num_acq_attempts <= 0 || attempt_num < num_acq_attempts);
+        }
+    }
+
+    /*
+     *  task we post to separate thread to remove
+     *  unspecified pooled resources
+     *
+     *  TODO: do removal and destruction synchronously
+     *        but carefully not synchronized during the
+     *        destruction of the resource.
+     */
+    class RemoveTask implements Runnable
+    {
+        public RemoveTask() 
+        { incrementPendingRemoves(); }
+
+        public void run()
+        {
+            try
+            {
+                synchronousRemoveArbitraryResource();
+                recheckResizePool();
+            }
+            finally
+            { decrementPendingRemoves(); }
+        }
+    }
+
+    class CullTask extends TimerTask
+    {
+        public void run()
+        {
+            try
+            {
+                if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED && logger.isLoggable( MLevel.FINER ))
+                    logger.log( MLevel.FINER, "Checking for expired resources - " + new Date() + " [" + BasicResourcePool.this + "]");
+                synchronized ( BasicResourcePool.this )
+                { cullExpired(); }
+            }
+            catch ( ResourceClosedException e ) // one of our async threads died
+            {
+                if ( Debug.DEBUG )
+                {
+                    if ( logger.isLoggable( MLevel.FINE ) )
+                        logger.log( MLevel.FINE, "a resource pool async thread died.", e );
+                }
+                unexpectedBreak();
+            }
+        }
+    }
+
+    // this is run by a single-threaded timer, so we don't have
+    // to worry about multiple threads executing the task at the same 
+    // time 
+    class CheckIdleResourcesTask extends TimerTask
+    {
+        public void run()
+        {
+            try
+            {
+                //System.err.println("c3p0-JENNIFER: refurbishing idle resources - " + new Date() + " [" + BasicResourcePool.this + "]");
+                if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED && logger.isLoggable(MLevel.FINER))
+                    logger.log(MLevel.FINER, "Refurbishing idle resources - " + new Date() + " [" + BasicResourcePool.this + "]");
+                synchronized ( BasicResourcePool.this )
+                { checkIdleResources(); }
+            }
+            catch ( ResourceClosedException e ) // one of our async threads died
+            {
+                //e.printStackTrace();
+                if ( Debug.DEBUG )
+                {
+                    if ( logger.isLoggable( MLevel.FINE ) )
+                        logger.log( MLevel.FINE, "a resource pool async thread died.", e );
+                }
+                unexpectedBreak();
+            }
+        }
+    }
+
+    class AsyncTestIdleResourceTask implements Runnable
+    {
+        // unchanging after ctor
+        Object resc;
+
+        // protected by this' lock
+        boolean pending = true;
+        boolean failed;
+
+        AsyncTestIdleResourceTask( Object resc )
+        { this.resc = resc; }
+
+        public void run()
+        {
+            assert !Thread.holdsLock( BasicResourcePool.this );
+
+            try
+            {
+                try
+                { 
+                    mgr.refurbishIdleResource( resc ); 
+                }
+                catch ( Exception e )
+                {
+                    if ( logger.isLoggable( MLevel.FINE ) )
+                        logger.log( MLevel.FINE, "BasicResourcePool: An idle resource is broken and will be purged. [" + resc + ']', e);
+
+                    synchronized (BasicResourcePool.this)
+                    {
+                        if ( managed.keySet().contains( resc ) ) //resc might have been culled as expired while we tested
+                        {
+                            removeResource( resc ); 
+                            ensureMinResources();
+                        }
+
+                        ++failed_idle_tests;
+                        setLastIdleCheckFailure(e);
+                    }
+                }
+            }
+            finally
+            {
+                synchronized (BasicResourcePool.this)
+                {
+                    idleCheckResources.remove( resc );
+                    BasicResourcePool.this.notifyAll();
+                }
+            }
+        }
+    }
+
+    final static class PunchCard
+    {
+        long acquisition_time;
+        long last_checkin_time;
+        long checkout_time;
+        Exception checkoutStackTraceException;
+
+        PunchCard()
+        {
+            this.acquisition_time = System.currentTimeMillis();
+            this.last_checkin_time = acquisition_time;
+            this.checkout_time = -1;
+            this.checkoutStackTraceException = null;
+        }
+    }
+
+//  static class CheckInProgressResourceHolder
+//  {
+//  Object checkResource;
+
+//  public synchronized void setCheckResource( Object resc )
+//  { 
+//  this.checkResource = resc; 
+//  this.notifyAll();
+//  }
+
+//  public void unsetCheckResource()
+//  { setCheckResource( null ); }
+
+//  /**
+//  * @return true if we actually had to wait
+//  */
+//  public synchronized boolean awaitNotInCheck( Object resc )
+//  {
+//  boolean had_to_wait = false;
+//  boolean set_interrupt = false;
+//  while ( checkResource == resc )
+//  {
+//  try
+//  {
+//  had_to_wait = true;
+//  this.wait(); 
+//  }
+//  catch ( InterruptedException e )
+//  { 
+//  e.printStackTrace();
+//  set_interrupt = true;
+//  }
+//  }
+//  if ( set_interrupt )
+//  Thread.currentThread().interrupt();
+//  return had_to_wait;
+//  }
+//  }
+}
+
diff --git a/src/classes/com/mchange/v2/resourcepool/BasicResourcePoolFactory.java b/src/classes/com/mchange/v2/resourcepool/BasicResourcePoolFactory.java
new file mode 100644
index 0000000..37f6f1d
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/BasicResourcePoolFactory.java
@@ -0,0 +1,362 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+import java.util.*;
+import com.mchange.v2.async.*;
+
+public class BasicResourcePoolFactory extends ResourcePoolFactory
+{
+    public static BasicResourcePoolFactory createNoEventSupportInstance( int num_task_threads )
+    { return createNoEventSupportInstance( null, null, num_task_threads ); }
+
+    public static BasicResourcePoolFactory createNoEventSupportInstance( AsynchronousRunner taskRunner, 
+									 Timer timer )
+    { return createNoEventSupportInstance( taskRunner, timer, ResourcePoolFactory.DEFAULT_NUM_TASK_THREADS ); }
+
+
+    private static BasicResourcePoolFactory createNoEventSupportInstance( AsynchronousRunner taskRunner, 
+									  Timer timer,
+									  int default_num_task_threads )
+    {
+	return new BasicResourcePoolFactory( taskRunner, 
+					     timer,
+					     default_num_task_threads,
+					     true );
+    }
+
+    int     start                         = -1;   //default to min
+    int     min                           = 1;
+    int     max                           = 12;
+    int     inc                           = 3;
+    int     retry_attempts                = -1;   //by default, retry acquisitions forever
+    int     retry_delay                   = 1000; //1 second
+    long    idle_resource_test_period     = -1;   //milliseconds, by default we don't test idle resources
+    long    max_age                       = -1;   //milliseconds, by default resources never expire
+    long    max_idle_time                 = -1;   //milliseconds, by default resources never expire
+    long    excess_max_idle_time          = -1;   //milliseconds, by default resources never expire
+    long    destroy_overdue_resc_time     = -1;   //milliseconds
+    long    expiration_enforcement_delay  = -1;   //automatic, we come up with a reasonable default based on time params
+
+    boolean break_on_acquisition_failure    = true;
+    boolean debug_store_checkout_stacktrace = false;
+
+    AsynchronousRunner taskRunner;
+    boolean            taskRunner_is_external;
+
+    RunnableQueue asyncEventQueue;
+    boolean       asyncEventQueue_is_external;
+
+    Timer   timer;
+    boolean timer_is_external;
+
+    int default_num_task_threads;
+
+    Set liveChildren;
+
+
+
+    //OLD
+//      Set rqUsers = null;
+//      SimpleRunnableQueue rq = null;
+
+//      Set timerUsers = null;
+//      Timer timer = null;
+    //END OLD
+
+    BasicResourcePoolFactory()
+    { this( null, null, null ); }
+
+    BasicResourcePoolFactory( AsynchronousRunner taskRunner, 
+			      RunnableQueue asyncEventQueue,  
+			      Timer timer )
+    { this ( taskRunner, asyncEventQueue, timer, DEFAULT_NUM_TASK_THREADS ); }
+
+    BasicResourcePoolFactory( int num_task_threads )
+    { this ( null, null, null, num_task_threads ); }
+
+    BasicResourcePoolFactory( AsynchronousRunner taskRunner, 
+			      Timer timer,
+			      int default_num_task_threads,
+			      boolean no_event_support)
+    { 
+	this( taskRunner, null,  timer, default_num_task_threads );
+	if (no_event_support)
+	    asyncEventQueue_is_external = true; //if it's null, and external, it simply won't exist...
+    }
+
+    BasicResourcePoolFactory( AsynchronousRunner taskRunner, 
+			      RunnableQueue asyncEventQueue,  
+			      Timer timer,
+			      int default_num_task_threads)
+    {  
+	this.taskRunner = taskRunner;
+	this.taskRunner_is_external = ( taskRunner != null );
+
+	this.asyncEventQueue = asyncEventQueue;
+	this.asyncEventQueue_is_external = ( asyncEventQueue != null );
+
+	this.timer = timer;
+	this.timer_is_external = ( timer != null );
+
+	this.default_num_task_threads = default_num_task_threads;
+    }
+
+    private void createThreadResources()
+    {
+	if (! taskRunner_is_external )
+	    {
+		//taskRunner = new RoundRobinAsynchronousRunner( default_num_task_threads, true );
+		taskRunner = new ThreadPoolAsynchronousRunner( default_num_task_threads, true );
+		if (! asyncEventQueue_is_external)
+		    asyncEventQueue = ((Queuable) taskRunner).asRunnableQueue();
+	    }
+	if (! asyncEventQueue_is_external)
+	    asyncEventQueue = new CarefulRunnableQueue( true, false );
+	if (! timer_is_external )
+	    timer = new Timer( true );
+
+	this.liveChildren = new HashSet();
+    }
+
+    private void destroyThreadResources()
+    {
+	if (! taskRunner_is_external )
+	    {
+		taskRunner.close();
+		taskRunner = null;
+	    }
+	if (! asyncEventQueue_is_external )
+	    {
+		asyncEventQueue.close();
+		asyncEventQueue = null;
+	    }
+	if (! timer_is_external )
+	    {
+		timer.cancel();
+		timer = null;
+	    }
+
+	this.liveChildren = null;
+    }
+
+//      synchronized RunnableQueue getSharedRunnableQueue( BasicResourcePool pool )
+//      {
+//  	if (rqUsers == null)
+//  	    {
+//  		rqUsers = new HashSet();
+//  		rq = new SimpleRunnableQueue(true);
+//  	    }
+//  	rqUsers.add( pool );
+//  	return rq;
+//      }
+    
+//      synchronized Timer getTimer( BasicResourcePool pool )
+//      {
+//  	if (timerUsers == null)
+//  	    {
+//  		timerUsers = new HashSet();
+//  		timer = new Timer( true );
+//  	    }
+//  	timerUsers.add( pool );
+//  	return timer;
+//      }
+
+    synchronized void markBroken( BasicResourcePool pool )
+    {
+	//System.err.println("markBroken -- liveChildren: " + liveChildren);
+	if (liveChildren != null) //keep this method idempotent!
+	    {
+		liveChildren.remove( pool );
+		if (liveChildren.isEmpty())
+		    destroyThreadResources();
+	    }
+//  	rqUsers.remove( pool );
+//  	if (rqUsers.size() == 0)
+//  	    {
+//  		rqUsers = null;
+//  		rq.close();
+//  		rq = null;
+//  	    }
+
+//  	timerUsers.remove( pool );
+//  	if (timerUsers.size() == 0)
+//  	    {
+//  		timerUsers = null;
+//  		timer.cancel();
+//  		timer = null;
+//  	    }
+    }
+    
+    /**
+     * If start is less than min, it will
+     * be ignored, and the pool will start
+     * with min.
+     */
+    public synchronized void setStart( int start )
+	throws ResourcePoolException
+    { this.start = start; }
+
+    public synchronized int getStart()
+	throws ResourcePoolException
+    { return start; } 
+
+    public synchronized void setMin( int min )
+	throws ResourcePoolException
+    { this.min = min; }
+
+    public synchronized int getMin()
+	throws ResourcePoolException
+    { return min; }
+
+    public synchronized void setMax( int max )
+	throws ResourcePoolException
+    { this.max = max; }
+
+    public synchronized int getMax()
+	throws ResourcePoolException
+    { return max; }
+
+    public synchronized void setIncrement( int inc )
+	throws ResourcePoolException
+    { this.inc = inc; }
+
+    public synchronized int getIncrement()
+	throws ResourcePoolException
+    { return inc; }
+
+    public synchronized void setAcquisitionRetryAttempts( int retry_attempts )
+	throws ResourcePoolException
+    { this.retry_attempts = retry_attempts; }
+
+    public synchronized int getAcquisitionRetryAttempts()
+	throws ResourcePoolException
+    { return retry_attempts; }
+
+    public synchronized void setAcquisitionRetryDelay( int retry_delay )
+	throws ResourcePoolException
+    { this.retry_delay = retry_delay; }
+
+    public synchronized int getAcquisitionRetryDelay()
+	throws ResourcePoolException
+    { return retry_delay; }
+
+    public synchronized void setIdleResourceTestPeriod( long test_period )
+    { this.idle_resource_test_period = test_period; }
+
+    public synchronized long getIdleResourceTestPeriod()
+    { return idle_resource_test_period; }
+
+    public synchronized void setResourceMaxAge( long max_age )
+	throws ResourcePoolException
+    { this.max_age = max_age; }
+
+    public synchronized long getResourceMaxAge()
+	throws ResourcePoolException
+    { return max_age; }
+
+    public synchronized void setResourceMaxIdleTime( long millis )
+	throws ResourcePoolException
+    { this.max_idle_time = millis; }
+
+    public synchronized long getResourceMaxIdleTime()
+	throws ResourcePoolException
+    { return max_idle_time; }
+
+    public synchronized void setExcessResourceMaxIdleTime( long millis )
+	throws ResourcePoolException
+    { this.excess_max_idle_time = millis; }
+
+    public synchronized long getExcessResourceMaxIdleTime()
+	throws ResourcePoolException
+    { return excess_max_idle_time; }
+
+    public synchronized long getDestroyOverdueResourceTime()
+	throws ResourcePoolException
+    { return destroy_overdue_resc_time; }
+
+    public synchronized void setDestroyOverdueResourceTime( long millis )
+	throws ResourcePoolException
+    { this.destroy_overdue_resc_time = millis; }
+
+    public synchronized void setExpirationEnforcementDelay( long expiration_enforcement_delay )
+	throws ResourcePoolException
+    { this.expiration_enforcement_delay = expiration_enforcement_delay; }
+
+    public synchronized long getExpirationEnforcementDelay()
+	throws ResourcePoolException
+    { return expiration_enforcement_delay; }
+
+    public synchronized void setBreakOnAcquisitionFailure( boolean break_on_acquisition_failure )
+	throws ResourcePoolException
+    { this.break_on_acquisition_failure = break_on_acquisition_failure; }
+
+    public synchronized boolean getBreakOnAcquisitionFailure()
+	throws ResourcePoolException
+    { return break_on_acquisition_failure; }
+
+    public synchronized void setDebugStoreCheckoutStackTrace( boolean debug_store_checkout_stacktrace )
+	throws ResourcePoolException
+    { this.debug_store_checkout_stacktrace = debug_store_checkout_stacktrace; }
+
+    public synchronized boolean getDebugStoreCheckoutStackTrace()
+	throws ResourcePoolException
+    { return debug_store_checkout_stacktrace; }
+
+    public synchronized ResourcePool createPool(ResourcePool.Manager mgr)
+	throws ResourcePoolException
+    {
+	if (liveChildren == null)
+	    createThreadResources();
+	//System.err.println("Created liveChildren: " + liveChildren);
+	ResourcePool child = new BasicResourcePool( mgr, 
+						    start, 
+						    min, 
+						    max, 
+						    inc, 
+						    retry_attempts, 
+						    retry_delay, 
+						    idle_resource_test_period,
+						    max_age, 
+						    max_idle_time,
+						    excess_max_idle_time,
+						    destroy_overdue_resc_time,
+						    expiration_enforcement_delay,
+						    break_on_acquisition_failure,
+						    debug_store_checkout_stacktrace,
+						    taskRunner,
+						    asyncEventQueue,
+						    timer,
+						    this );
+	liveChildren.add( child );
+	return child;
+    }
+}
+
+
+
+
+
+
+
diff --git a/src/classes/com/mchange/v2/resourcepool/CannotAcquireResourceException.java b/src/classes/com/mchange/v2/resourcepool/CannotAcquireResourceException.java
new file mode 100644
index 0000000..ebf719c
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/CannotAcquireResourceException.java
@@ -0,0 +1,39 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+public class CannotAcquireResourceException extends ResourcePoolException
+{
+    public CannotAcquireResourceException(String msg, Throwable t)
+    {super(msg, t);}
+
+    public CannotAcquireResourceException(Throwable t)
+    {super(t);}
+
+    public CannotAcquireResourceException(String msg)
+    {super(msg);}
+
+    public CannotAcquireResourceException()
+    {super();}
+}
diff --git a/src/classes/com/mchange/v2/resourcepool/ResourcePool.java b/src/classes/com/mchange/v2/resourcepool/ResourcePool.java
new file mode 100644
index 0000000..79a7920
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/ResourcePool.java
@@ -0,0 +1,141 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+import com.mchange.v1.util.ClosableResource;
+
+public interface ResourcePool extends ClosableResource
+{
+    // status in pool return values
+    final static int KNOWN_AND_AVAILABLE   = 0;
+    final static int KNOWN_AND_CHECKED_OUT = 1;
+    final static int UNKNOWN_OR_PURGED     = -1;
+
+    public Object checkoutResource()
+	throws ResourcePoolException, InterruptedException;
+
+    public Object checkoutResource( long timeout )
+	throws TimeoutException, ResourcePoolException, InterruptedException;
+
+    public void checkinResource( Object resc ) 
+	throws ResourcePoolException;
+
+    public void checkinAll()
+	throws ResourcePoolException;
+
+    public int statusInPool( Object resc )
+	throws ResourcePoolException;
+
+    /**
+     * Marks a resource as broken. If the resource is checked in,
+     * it will be destroyed immediately. Otherwise, it will be
+     * destroyed on checkin.
+     */
+    public void markBroken( Object resc ) 
+	throws ResourcePoolException;
+
+    public int getMinPoolSize()
+	throws ResourcePoolException;
+
+    public int getMaxPoolSize()
+	throws ResourcePoolException;
+
+    public int getPoolSize()
+	throws ResourcePoolException;
+
+    public void setPoolSize(int size)
+	throws ResourcePoolException;
+
+    public int getAvailableCount()
+	throws ResourcePoolException;
+
+    public int getExcludedCount()
+	throws ResourcePoolException;
+
+    public int getAwaitingCheckinCount()
+	throws ResourcePoolException;
+
+    public long getEffectiveExpirationEnforcementDelay()
+    throws ResourcePoolException;
+    
+    public long getStartTime()
+    throws ResourcePoolException;
+    
+    public long getUpTime()
+    throws ResourcePoolException;
+    
+    public long getNumFailedCheckins()
+    throws ResourcePoolException;
+
+    public long getNumFailedCheckouts()
+    throws ResourcePoolException;
+
+    public long getNumFailedIdleTests()
+    throws ResourcePoolException;
+    
+    public int getNumCheckoutWaiters()
+    throws ResourcePoolException;
+
+    public Throwable getLastAcquisitionFailure()
+    throws ResourcePoolException;
+
+    public Throwable getLastCheckinFailure()
+    throws ResourcePoolException;
+
+    public Throwable getLastCheckoutFailure()
+    throws ResourcePoolException;
+
+    public Throwable getLastIdleCheckFailure()
+    throws ResourcePoolException;
+    
+    public Throwable getLastResourceTestFailure()
+    throws ResourcePoolException;
+
+    
+
+    /**
+     * Discards all resources managed by the pool
+     * and reacquires new resources to populate the
+     * pool. Current checked out resources will still
+     * be valid, and should still be checked into the
+     * pool (so the pool can destroy them).
+     */
+    public void resetPool()
+	throws ResourcePoolException;
+
+    public void close() 
+	throws ResourcePoolException;
+
+    public void close( boolean close_checked_out_resources ) 
+	throws ResourcePoolException;
+
+    public interface Manager
+    {
+	public Object acquireResource() throws Exception;
+	public void   refurbishIdleResource(Object resc) throws Exception;
+	public void   refurbishResourceOnCheckout(Object resc) throws Exception;
+	public void   refurbishResourceOnCheckin(Object resc) throws Exception;
+	public void   destroyResource(Object resc) throws Exception;
+    }
+}
diff --git a/src/classes/com/mchange/v2/resourcepool/ResourcePoolEvent.java b/src/classes/com/mchange/v2/resourcepool/ResourcePoolEvent.java
new file mode 100644
index 0000000..5a636ff
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/ResourcePoolEvent.java
@@ -0,0 +1,75 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+import java.util.EventObject;
+
+public class ResourcePoolEvent extends EventObject
+{
+    Object  resc;
+    boolean checked_out_resource;
+    int     pool_size;
+    int     available_size;
+    int     removed_but_unreturned_size;
+
+    public ResourcePoolEvent( ResourcePool pool,
+			      Object       resc,
+			      boolean      checked_out_resource,
+			      int          pool_size,
+			      int          available_size,
+			      int          removed_but_unreturned_size )
+    {
+	super(pool);
+	this.resc = resc;
+	this.checked_out_resource = checked_out_resource;
+	this.pool_size = pool_size;
+	this.available_size = available_size;
+	this.removed_but_unreturned_size = removed_but_unreturned_size;
+    }
+
+    public Object getResource()
+    { return resc; }
+
+    public boolean isCheckedOutResource()
+    { return checked_out_resource; }
+
+    public int getPoolSize()
+    { return pool_size; }
+
+    public int getAvailableSize()
+    { return available_size; }
+
+    public int getRemovedButUnreturnedSize()
+    { return removed_but_unreturned_size; }
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/classes/com/mchange/v2/resourcepool/ResourcePoolEventSupport.java b/src/classes/com/mchange/v2/resourcepool/ResourcePoolEventSupport.java
new file mode 100644
index 0000000..1afeff8
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/ResourcePoolEventSupport.java
@@ -0,0 +1,129 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+import java.util.*;
+
+public class ResourcePoolEventSupport
+{
+    ResourcePool source;
+    Set          mlisteners = new HashSet();
+
+    public ResourcePoolEventSupport(ResourcePool source)
+    { this.source = source; }
+
+    public synchronized void addResourcePoolListener(ResourcePoolListener mlistener)
+    {mlisteners.add(mlistener);}
+
+    public synchronized void removeResourcePoolListener(ResourcePoolListener mlistener)
+    {mlisteners.remove(mlistener);}
+
+    public synchronized void fireResourceAcquired( Object       resc,
+						   int          pool_size,
+						   int          available_size,
+						   int          removed_but_unreturned_size )
+    {
+	if (! mlisteners.isEmpty() )
+	    {
+		ResourcePoolEvent evt = new ResourcePoolEvent(source,
+							      resc,
+							      false,
+							      pool_size,
+							      available_size,
+							      removed_but_unreturned_size );
+		for (Iterator i = mlisteners.iterator(); i.hasNext();)
+		    {
+			ResourcePoolListener rpl = (ResourcePoolListener) i.next();
+			rpl.resourceAcquired(evt);
+		    }
+	    }
+    }
+
+    public synchronized void fireResourceCheckedIn( Object       resc,
+						    int          pool_size,
+						    int          available_size,
+						    int          removed_but_unreturned_size )
+    {
+	if (! mlisteners.isEmpty() )
+	    {
+		ResourcePoolEvent evt = new ResourcePoolEvent(source,
+							      resc,
+							      false,
+							      pool_size,
+							      available_size,
+							      removed_but_unreturned_size );
+		for (Iterator i = mlisteners.iterator(); i.hasNext();)
+		    {
+			ResourcePoolListener rpl = (ResourcePoolListener) i.next();
+			rpl.resourceCheckedIn(evt);
+		    }
+	    }
+    }
+
+    public synchronized void fireResourceCheckedOut( Object       resc,
+						     int          pool_size,
+						     int          available_size,
+						     int          removed_but_unreturned_size )
+    {
+	if (! mlisteners.isEmpty() )
+	    {
+		ResourcePoolEvent evt = new ResourcePoolEvent(source,
+							      resc,
+							      true,
+							      pool_size,
+							      available_size,
+							      removed_but_unreturned_size );
+		for (Iterator i = mlisteners.iterator(); i.hasNext();)
+		    {
+			ResourcePoolListener rpl = (ResourcePoolListener) i.next();
+			rpl.resourceCheckedOut(evt);
+		    }
+	    }
+    }
+
+    public synchronized void fireResourceRemoved( Object       resc,
+						  boolean      checked_out_resource,
+						  int          pool_size,
+						  int          available_size,
+						  int          removed_but_unreturned_size )
+    {
+	if (! mlisteners.isEmpty() )
+	    {
+		ResourcePoolEvent evt = new ResourcePoolEvent(source,
+							      resc,
+							      checked_out_resource,
+							      pool_size,
+							      available_size,
+							      removed_but_unreturned_size );
+		for (Iterator i = mlisteners.iterator(); i.hasNext();)
+		    {
+			ResourcePoolListener rpl = (ResourcePoolListener) i.next();
+			rpl.resourceRemoved(evt);
+		    }
+	    }
+    }
+}
+
+
+
diff --git a/src/classes/com/mchange/v2/resourcepool/ResourcePoolException.java b/src/classes/com/mchange/v2/resourcepool/ResourcePoolException.java
new file mode 100644
index 0000000..f21c7ec
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/ResourcePoolException.java
@@ -0,0 +1,41 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+import com.mchange.lang.PotentiallySecondaryException;
+
+public class ResourcePoolException extends PotentiallySecondaryException
+{
+    public ResourcePoolException(String msg, Throwable t)
+    {super(msg, t);}
+
+    public ResourcePoolException(Throwable t)
+    {super(t);}
+
+    public ResourcePoolException(String msg)
+    {super(msg);}
+
+    public ResourcePoolException()
+    {super();}
+}
diff --git a/src/classes/com/mchange/v2/resourcepool/ResourcePoolFactory.java b/src/classes/com/mchange/v2/resourcepool/ResourcePoolFactory.java
new file mode 100644
index 0000000..eb34a59
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/ResourcePoolFactory.java
@@ -0,0 +1,189 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+import java.util.Timer;
+import com.mchange.v2.async.*;
+
+/**
+ *  <P>A Factory for ResourcePools. ResourcePoolFactories may manage
+ *     resources (usually threads that perform maintenance tasks) that
+ *     are shared by all pools that they create. Clients who require
+ *     a large number of pools may wish to create their own factory
+ *     instances rather than using the shared instance to control
+ *     the degree of resource (Thread) sharing among pools.</P>
+ *
+ *  <P>Factories should (and the default implementation does) be careful
+ *     to ensure that factories keep resources open only while there
+ *     are active ResourcePools that they have created.</P>
+ *
+ *  <P>Subclasses must mark all methods synchronized so that clients
+ *     may reliably use shared factories to do stuff like...</P>
+ *    
+ *  <pre>
+ *     synchronized (factory)
+ *     {
+ *         factory.setMin(8);
+ *         factory.setMax(24);
+ *         ResourcePool rp = factory.createPool();
+ *     }
+ *  </pre>
+ */
+public abstract class ResourcePoolFactory
+{
+    // okay, 'cuz we don't actually create any threads / resourced
+    // until the factory is used.
+    final static ResourcePoolFactory SHARED_INSTANCE = new BasicResourcePoolFactory();
+
+    final static int DEFAULT_NUM_TASK_THREADS = 3;
+
+    public static ResourcePoolFactory getSharedInstance()
+	throws ResourcePoolException
+    { return SHARED_INSTANCE; }
+
+    public static ResourcePoolFactory createInstance()
+    { return new BasicResourcePoolFactory(); }
+
+    public static ResourcePoolFactory createInstance( int num_task_threads )
+    { return new BasicResourcePoolFactory( num_task_threads ); }
+
+    /**
+     * Any or all of these arguments can be null -- any unspecified resources
+     * will be created and cleaned up internally.
+     */
+    public static ResourcePoolFactory createInstance( AsynchronousRunner taskRunner,
+						      RunnableQueue asyncEventQueue,
+						      Timer cullTimer )
+    { return new BasicResourcePoolFactory( taskRunner, asyncEventQueue, cullTimer ); }
+
+    public static ResourcePoolFactory createInstance( Queuable taskRunnerEventQueue,
+						      Timer cullTimer )
+    {
+	return createInstance( taskRunnerEventQueue, 
+			       taskRunnerEventQueue == null ?
+			       null :
+			       taskRunnerEventQueue.asRunnableQueue(),
+			       cullTimer );
+    }
+
+    public abstract void setMin( int min )
+	throws ResourcePoolException;
+
+    public abstract int getMin()
+	throws ResourcePoolException;
+
+    public abstract void setMax( int max )
+	throws ResourcePoolException;
+
+    public abstract int getStart()
+	throws ResourcePoolException;
+
+    public abstract void setStart( int start )
+	throws ResourcePoolException;
+
+    public abstract int getMax()
+	throws ResourcePoolException;
+
+    public abstract void setIncrement( int max )
+	throws ResourcePoolException;
+
+    public abstract int getIncrement()
+	throws ResourcePoolException;
+
+    public abstract void setAcquisitionRetryAttempts( int retry_attempts )
+	throws ResourcePoolException;
+
+    public abstract int getAcquisitionRetryAttempts()
+	throws ResourcePoolException;
+
+    public abstract void setAcquisitionRetryDelay( int retry_delay )
+	throws ResourcePoolException;
+
+    public abstract int getAcquisitionRetryDelay()
+	throws ResourcePoolException;
+
+    public abstract void setIdleResourceTestPeriod( long test_period )
+	throws ResourcePoolException;
+
+    public abstract long getIdleResourceTestPeriod()
+	throws ResourcePoolException;
+
+    public abstract void setResourceMaxAge( long millis )
+	throws ResourcePoolException;
+
+    public abstract long getResourceMaxAge()
+	throws ResourcePoolException;
+
+    public abstract void setResourceMaxIdleTime( long millis )
+	throws ResourcePoolException;
+
+    public abstract long getResourceMaxIdleTime()
+	throws ResourcePoolException;
+
+    public abstract void setExcessResourceMaxIdleTime( long millis )
+	throws ResourcePoolException;
+
+    public abstract long getExcessResourceMaxIdleTime()
+	throws ResourcePoolException;
+
+    public abstract long getDestroyOverdueResourceTime()
+	throws ResourcePoolException;
+
+    public abstract void setDestroyOverdueResourceTime( long millis )
+	throws ResourcePoolException;
+
+    public abstract void setExpirationEnforcementDelay( long millis )
+	throws ResourcePoolException;
+
+    public abstract long getExpirationEnforcementDelay()
+	throws ResourcePoolException;
+
+    public abstract void setBreakOnAcquisitionFailure( boolean b )
+	throws ResourcePoolException;
+
+    public abstract boolean getBreakOnAcquisitionFailure()
+	throws ResourcePoolException;
+
+    public abstract void setDebugStoreCheckoutStackTrace( boolean debug_store_checkout_stacktrace )
+	throws ResourcePoolException;
+
+    public abstract boolean getDebugStoreCheckoutStackTrace()
+	throws ResourcePoolException;
+
+//     /**
+//      *  Sets whether or not maxAge should be interpreted
+//      *  as the maximum age since the resource was first acquired 
+//      *  (age_is_absolute == true) or since the resource was last
+//      *  checked in (age_is_absolute == false).
+//      */
+//     public abstract void setAgeIsAbsolute( boolean age_is_absolute )
+// 	throws ResourcePoolException;
+
+//     public abstract boolean getAgeIsAbsolute()
+// 	throws ResourcePoolException;
+
+    public abstract ResourcePool createPool(ResourcePool.Manager mgr)
+	throws ResourcePoolException;
+}
+
diff --git a/src/classes/com/mchange/v2/resourcepool/ResourcePoolListener.java b/src/classes/com/mchange/v2/resourcepool/ResourcePoolListener.java
new file mode 100644
index 0000000..5219935
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/ResourcePoolListener.java
@@ -0,0 +1,37 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+import java.util.EventListener;
+
+public interface ResourcePoolListener extends EventListener
+{
+    public void resourceAcquired(ResourcePoolEvent evt);
+    
+    public void resourceCheckedIn(ResourcePoolEvent evt);
+
+    public void resourceCheckedOut(ResourcePoolEvent evt);
+
+    public void resourceRemoved(ResourcePoolEvent evt);
+}
diff --git a/src/classes/com/mchange/v2/resourcepool/ResourcePoolUtils.java b/src/classes/com/mchange/v2/resourcepool/ResourcePoolUtils.java
new file mode 100644
index 0000000..4e468e8
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/ResourcePoolUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+import com.mchange.v2.log.*;
+
+final class ResourcePoolUtils
+{
+    final static MLogger logger = MLog.getLogger( ResourcePoolUtils.class );
+
+    final static ResourcePoolException convertThrowable( String msg, Throwable t )
+    {
+	if (Debug.DEBUG)
+	    {
+		//t.printStackTrace();
+		if (logger.isLoggable( MLevel.FINE ) )
+		    logger.log( MLevel.FINE , "Converting throwable to ResourcePoolException..." , t );
+	    }
+	if ( t instanceof ResourcePoolException)
+	    return (ResourcePoolException) t;
+	else
+	    return new ResourcePoolException( msg, t );
+    }
+
+    final static ResourcePoolException convertThrowable( Throwable t )
+    { return convertThrowable("Ouch! " + t.toString(), t ); }
+}
diff --git a/src/classes/com/mchange/v2/resourcepool/TimeoutException.java b/src/classes/com/mchange/v2/resourcepool/TimeoutException.java
new file mode 100644
index 0000000..f0b1b74
--- /dev/null
+++ b/src/classes/com/mchange/v2/resourcepool/TimeoutException.java
@@ -0,0 +1,39 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.resourcepool;
+
+public class TimeoutException extends ResourcePoolException 
+{
+    public TimeoutException(String msg, Throwable t)
+    {super(msg, t);}
+
+    public TimeoutException(Throwable t)
+    {super(t);}
+
+    public TimeoutException(String msg)
+    {super(msg);}
+
+    public TimeoutException()
+    {super();}
+}
diff --git a/src/classes/com/mchange/v2/ser/IndirectPolicy.java b/src/classes/com/mchange/v2/ser/IndirectPolicy.java
new file mode 100644
index 0000000..3acb929
--- /dev/null
+++ b/src/classes/com/mchange/v2/ser/IndirectPolicy.java
@@ -0,0 +1,39 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.ser;
+
+public final class IndirectPolicy
+{
+    public final static IndirectPolicy DEFINITELY_INDIRECT   = new IndirectPolicy("DEFINITELY_INDIRECT");
+    public final static IndirectPolicy INDIRECT_ON_EXCEPTION = new IndirectPolicy("INDIRECT_ON_EXCEPTION");
+    public final static IndirectPolicy DEFINITELY_DIRECT     = new IndirectPolicy("DEFINITELY_DIRECT");
+
+    String name;
+
+    private IndirectPolicy(String name)
+    { this.name = name; }
+    
+    public String toString()
+    { return "[IndirectPolicy: " + name + ']'; }
+}
diff --git a/src/classes/com/mchange/v2/ser/IndirectlySerialized.java b/src/classes/com/mchange/v2/ser/IndirectlySerialized.java
new file mode 100644
index 0000000..6e46222
--- /dev/null
+++ b/src/classes/com/mchange/v2/ser/IndirectlySerialized.java
@@ -0,0 +1,33 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.ser;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+public interface IndirectlySerialized extends Serializable
+{
+    public Object getObject() throws ClassNotFoundException, IOException;
+}
+
diff --git a/src/classes/com/mchange/v2/ser/Indirector.java b/src/classes/com/mchange/v2/ser/Indirector.java
new file mode 100644
index 0000000..c40fcb4
--- /dev/null
+++ b/src/classes/com/mchange/v2/ser/Indirector.java
@@ -0,0 +1,29 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.ser;
+
+public interface Indirector
+{
+    public IndirectlySerialized indirectForm( Object orig ) throws Exception;
+}
diff --git a/src/classes/com/mchange/v2/ser/SerializableUtils.java b/src/classes/com/mchange/v2/ser/SerializableUtils.java
new file mode 100644
index 0000000..e659d41
--- /dev/null
+++ b/src/classes/com/mchange/v2/ser/SerializableUtils.java
@@ -0,0 +1,171 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.ser;
+
+import java.io.*;
+import com.mchange.v1.io.*;
+import com.mchange.v2.log.*;
+
+public final class SerializableUtils
+{
+    final static MLogger logger = MLog.getLogger( SerializableUtils.class );
+
+    private SerializableUtils()
+    {}
+
+
+    public static byte[] toByteArray(Object obj) throws NotSerializableException
+    { return serializeToByteArray( obj ); }
+
+    public static byte[] toByteArray(Object obj, Indirector indirector, IndirectPolicy policy) throws NotSerializableException
+    {
+	try
+	    {
+		if (policy == IndirectPolicy.DEFINITELY_INDIRECT)
+		    {
+			if (indirector == null)
+			    throw new IllegalArgumentException("null indirector is not consistent with " + policy);
+
+			IndirectlySerialized indirect = indirector.indirectForm( obj );
+			return toByteArray( indirect );
+		    }
+		else if ( policy == IndirectPolicy.INDIRECT_ON_EXCEPTION )
+		    {
+			if (indirector == null)
+			    throw new IllegalArgumentException("null indirector is not consistent with " + policy);
+
+			try { return toByteArray( obj ); }
+			catch ( NotSerializableException e )
+			    { return toByteArray( obj, indirector, IndirectPolicy.DEFINITELY_INDIRECT ); }
+		    }
+		else if (policy == IndirectPolicy.DEFINITELY_DIRECT)
+		    return toByteArray( obj );
+		else
+		    throw new InternalError("unknown indirecting policy: " + policy);
+	    }
+	catch ( NotSerializableException e )
+	    { throw e; }
+	catch ( Exception e )
+	    {
+		//e.printStackTrace();
+		if ( logger.isLoggable( MLevel.WARNING ) )
+		    logger.log( MLevel.WARNING, "An Exception occurred while serializing an Object to a byte[] with an Indirector.", e );
+		throw new NotSerializableException( e.toString() );
+	    }
+    }
+
+    /**
+     * @deprecated use SerialializableUtils.toByteArray() [shorter name is better!]
+     */
+    public static byte[] serializeToByteArray(Object obj) throws NotSerializableException
+    {
+	try
+	{
+	    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+	    ObjectOutputStream    out  = new ObjectOutputStream(baos);
+	    out.writeObject(obj);
+	    return baos.toByteArray();
+	}
+	catch (NotSerializableException e)
+	{
+	    //this is the only IOException that 
+	    //shouldn't signal a bizarre error...
+	    e.fillInStackTrace();
+	    throw e;
+	}
+	catch (IOException e)
+	{
+	    //e.printStackTrace();
+	    if ( logger.isLoggable( MLevel.SEVERE ) )
+		logger.log( MLevel.SEVERE, "An IOException occurred while writing into a ByteArrayOutputStream?!?", e );
+	    throw new Error("IOException writing to a byte array!");
+	}
+    }
+    
+    /**
+     * By default, unwraps IndirectlySerialized objects, returning the original
+     */
+    public static Object fromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
+    { 
+	Object out = deserializeFromByteArray( bytes ); 
+	if (out instanceof IndirectlySerialized)
+	    return ((IndirectlySerialized) out).getObject();
+	else
+	    return out;
+    }
+
+    public static Object fromByteArray(byte[] bytes, boolean ignore_indirects) throws IOException, ClassNotFoundException
+    { 
+	if (ignore_indirects)
+	    return deserializeFromByteArray( bytes ); 
+	else
+	    return fromByteArray( bytes );
+    }
+
+    /**
+     * @deprecated use SerialializableUtils.fromByteArray() [shorter name is better!]
+     */
+    public static Object deserializeFromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
+    {
+	ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
+	return in.readObject();
+    }
+
+
+    public static Object testSerializeDeserialize( Object o ) throws IOException, ClassNotFoundException
+    { return deepCopy( o ); }
+
+    public static Object deepCopy( Object o ) throws IOException, ClassNotFoundException
+    {
+	byte[] bytes = serializeToByteArray( o );
+	return deserializeFromByteArray( bytes );
+    }
+
+    public final static Object unmarshallObjectFromFile(File file) 
+	throws IOException, ClassNotFoundException
+    {
+      ObjectInputStream in = null;
+      try
+	{
+	  in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
+	  return in.readObject();
+	}
+      finally
+	{InputStreamUtils.attemptClose(in);}
+    }
+
+  public final static void marshallObjectToFile(Object o, File file) 
+      throws IOException
+    {
+      ObjectOutputStream out = null;
+      try
+	{
+	  out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
+	  out.writeObject(o);
+	}
+      finally
+	{OutputStreamUtils.attemptClose(out);}
+    }
+}
+
diff --git a/src/classes/com/mchange/v2/ser/UnsupportedVersionException.java b/src/classes/com/mchange/v2/ser/UnsupportedVersionException.java
new file mode 100644
index 0000000..ac86db8
--- /dev/null
+++ b/src/classes/com/mchange/v2/ser/UnsupportedVersionException.java
@@ -0,0 +1,35 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.ser;
+
+import java.io.*;
+
+public class UnsupportedVersionException extends InvalidClassException
+{
+  public UnsupportedVersionException(String message)
+  {super(message);}
+
+  public UnsupportedVersionException(Object obj, int version)
+  {this(obj.getClass().getName() + " -- unsupported version: " + version);}
+}
diff --git a/src/classes/com/mchange/v2/sql/SqlUtils.java b/src/classes/com/mchange/v2/sql/SqlUtils.java
new file mode 100644
index 0000000..ee4e99b
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/SqlUtils.java
@@ -0,0 +1,118 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql;
+
+import java.sql.*;
+import com.mchange.v2.log.*;
+
+import java.util.Date;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import com.mchange.lang.ThrowableUtils;
+import com.mchange.v2.lang.VersionUtils;
+
+public final class SqlUtils
+{
+    final static MLogger logger = MLog.getLogger( SqlUtils.class );
+
+    // protected by SqlUtils.class' lock
+    final static DateFormat tsdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS");
+
+    public final static String DRIVER_MANAGER_USER_PROPERTY     = "user";
+    public final static String DRIVER_MANAGER_PASSWORD_PROPERTY = "password";
+
+    public static String escapeBadSqlPatternChars(String s)
+    {
+	StringBuffer sb = new StringBuffer(s);
+	for (int i = 0, len = sb.length(); i < len; ++i)
+	    if (sb.charAt(i) == '\'')
+		{
+		    sb.insert(i, '\'');
+		    ++len;
+		    i+=2;
+		}
+	return sb.toString();
+    }
+
+    public synchronized static String escapeAsTimestamp( Date date )
+    { return "{ts '" + tsdf.format( date ) + "'}";  }
+
+    public static SQLException toSQLException(Throwable t)
+    { return toSQLException(null, t ); }
+
+    public static SQLException toSQLException(String msg, Throwable t)
+    { return toSQLException(msg, null, t);}
+
+    public static SQLException toSQLException(String msg, String sqlState, Throwable t)
+    {
+        if (t instanceof SQLException)
+	    {
+		if (Debug.DEBUG && 
+		    Debug.TRACE == Debug.TRACE_MAX && 
+		    logger.isLoggable( MLevel.FINER ))
+		    {
+			SQLException s = (SQLException) t;
+			StringBuffer tmp = new StringBuffer(255);
+			tmp.append("Attempted to convert SQLException to SQLException. Leaving it alone.");
+			tmp.append(" [SQLState: ");
+			tmp.append( s.getSQLState() );
+			tmp.append("; errorCode: " );
+			tmp.append( s.getErrorCode() );
+			tmp.append(']');
+			if (msg != null)
+			    tmp.append(" Ignoring suggested message: '" + msg + "'.");
+			logger.log( MLevel.FINER, tmp.toString(), t );
+
+			SQLException s2 = s;
+			while ((s2 = s2.getNextException()) != null)
+			    logger.log( MLevel.FINER, "Nested SQLException or SQLWarning: ", s2 );
+		    }
+		return (SQLException) t;
+	    }
+        else
+        { 
+            if (Debug.DEBUG) 
+		{
+		    //t.printStackTrace();
+		    if ( logger.isLoggable( MLevel.FINE ) )
+			logger.log( MLevel.FINE, "Converting Throwable to SQLException...", t );
+		}
+
+	    if (msg == null)
+		msg = "An SQLException was provoked by the following failure: " + t.toString();
+	    if ( VersionUtils.isAtLeastJavaVersion14() )
+		{
+		    SQLException out = new SQLException(msg);
+		    out.initCause( t );
+		    return out;
+		}
+	    else
+		return new SQLException( msg + System.getProperty( "line.separator" ) +
+					 "[Cause: " + ThrowableUtils.extractStackTrace(t) + ']', sqlState); 
+        }
+    }
+    
+    private SqlUtils()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/FilterCallableStatement.java b/src/classes/com/mchange/v2/sql/filter/FilterCallableStatement.java
new file mode 100644
index 0000000..5bfcb7c
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/FilterCallableStatement.java
@@ -0,0 +1,523 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.Object;
+import java.lang.String;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.ParameterMetaData;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Map;
+
+public abstract class FilterCallableStatement implements CallableStatement
+{
+	protected CallableStatement inner;
+	
+	public FilterCallableStatement(CallableStatement inner)
+	{ this.inner = inner; }
+	
+	public FilterCallableStatement()
+	{}
+	
+	public void setInner( CallableStatement inner )
+	{ this.inner = inner; }
+	
+	public CallableStatement getInner()
+	{ return inner; }
+	
+	public boolean wasNull() throws SQLException
+	{ return inner.wasNull(); }
+	
+	public BigDecimal getBigDecimal(int a, int b) throws SQLException
+	{ return inner.getBigDecimal(a, b); }
+	
+	public BigDecimal getBigDecimal(int a) throws SQLException
+	{ return inner.getBigDecimal(a); }
+	
+	public BigDecimal getBigDecimal(String a) throws SQLException
+	{ return inner.getBigDecimal(a); }
+	
+	public Timestamp getTimestamp(String a) throws SQLException
+	{ return inner.getTimestamp(a); }
+	
+	public Timestamp getTimestamp(String a, Calendar b) throws SQLException
+	{ return inner.getTimestamp(a, b); }
+	
+	public Timestamp getTimestamp(int a, Calendar b) throws SQLException
+	{ return inner.getTimestamp(a, b); }
+	
+	public Timestamp getTimestamp(int a) throws SQLException
+	{ return inner.getTimestamp(a); }
+	
+	public Blob getBlob(String a) throws SQLException
+	{ return inner.getBlob(a); }
+	
+	public Blob getBlob(int a) throws SQLException
+	{ return inner.getBlob(a); }
+	
+	public Clob getClob(String a) throws SQLException
+	{ return inner.getClob(a); }
+	
+	public Clob getClob(int a) throws SQLException
+	{ return inner.getClob(a); }
+	
+	public void setNull(String a, int b, String c) throws SQLException
+	{ inner.setNull(a, b, c); }
+	
+	public void setNull(String a, int b) throws SQLException
+	{ inner.setNull(a, b); }
+	
+	public void setBigDecimal(String a, BigDecimal b) throws SQLException
+	{ inner.setBigDecimal(a, b); }
+	
+	public void setBytes(String a, byte[] b) throws SQLException
+	{ inner.setBytes(a, b); }
+	
+	public void setTimestamp(String a, Timestamp b, Calendar c) throws SQLException
+	{ inner.setTimestamp(a, b, c); }
+	
+	public void setTimestamp(String a, Timestamp b) throws SQLException
+	{ inner.setTimestamp(a, b); }
+	
+	public void setAsciiStream(String a, InputStream b, int c) throws SQLException
+	{ inner.setAsciiStream(a, b, c); }
+	
+	public void setBinaryStream(String a, InputStream b, int c) throws SQLException
+	{ inner.setBinaryStream(a, b, c); }
+	
+	public void setObject(String a, Object b) throws SQLException
+	{ inner.setObject(a, b); }
+	
+	public void setObject(String a, Object b, int c, int d) throws SQLException
+	{ inner.setObject(a, b, c, d); }
+	
+	public void setObject(String a, Object b, int c) throws SQLException
+	{ inner.setObject(a, b, c); }
+	
+	public void setCharacterStream(String a, Reader b, int c) throws SQLException
+	{ inner.setCharacterStream(a, b, c); }
+	
+	public void registerOutParameter(String a, int b) throws SQLException
+	{ inner.registerOutParameter(a, b); }
+	
+	public void registerOutParameter(int a, int b) throws SQLException
+	{ inner.registerOutParameter(a, b); }
+	
+	public void registerOutParameter(int a, int b, int c) throws SQLException
+	{ inner.registerOutParameter(a, b, c); }
+	
+	public void registerOutParameter(int a, int b, String c) throws SQLException
+	{ inner.registerOutParameter(a, b, c); }
+	
+	public void registerOutParameter(String a, int b, int c) throws SQLException
+	{ inner.registerOutParameter(a, b, c); }
+	
+	public void registerOutParameter(String a, int b, String c) throws SQLException
+	{ inner.registerOutParameter(a, b, c); }
+	
+	public Object getObject(String a, Map b) throws SQLException
+	{ return inner.getObject(a, b); }
+	
+	public Object getObject(int a, Map b) throws SQLException
+	{ return inner.getObject(a, b); }
+	
+	public Object getObject(int a) throws SQLException
+	{ return inner.getObject(a); }
+	
+	public Object getObject(String a) throws SQLException
+	{ return inner.getObject(a); }
+	
+	public boolean getBoolean(int a) throws SQLException
+	{ return inner.getBoolean(a); }
+	
+	public boolean getBoolean(String a) throws SQLException
+	{ return inner.getBoolean(a); }
+	
+	public byte getByte(String a) throws SQLException
+	{ return inner.getByte(a); }
+	
+	public byte getByte(int a) throws SQLException
+	{ return inner.getByte(a); }
+	
+	public short getShort(int a) throws SQLException
+	{ return inner.getShort(a); }
+	
+	public short getShort(String a) throws SQLException
+	{ return inner.getShort(a); }
+	
+	public int getInt(String a) throws SQLException
+	{ return inner.getInt(a); }
+	
+	public int getInt(int a) throws SQLException
+	{ return inner.getInt(a); }
+	
+	public long getLong(int a) throws SQLException
+	{ return inner.getLong(a); }
+	
+	public long getLong(String a) throws SQLException
+	{ return inner.getLong(a); }
+	
+	public float getFloat(String a) throws SQLException
+	{ return inner.getFloat(a); }
+	
+	public float getFloat(int a) throws SQLException
+	{ return inner.getFloat(a); }
+	
+	public double getDouble(String a) throws SQLException
+	{ return inner.getDouble(a); }
+	
+	public double getDouble(int a) throws SQLException
+	{ return inner.getDouble(a); }
+	
+	public byte[] getBytes(int a) throws SQLException
+	{ return inner.getBytes(a); }
+	
+	public byte[] getBytes(String a) throws SQLException
+	{ return inner.getBytes(a); }
+	
+	public URL getURL(String a) throws SQLException
+	{ return inner.getURL(a); }
+	
+	public URL getURL(int a) throws SQLException
+	{ return inner.getURL(a); }
+	
+	public void setBoolean(String a, boolean b) throws SQLException
+	{ inner.setBoolean(a, b); }
+	
+	public void setByte(String a, byte b) throws SQLException
+	{ inner.setByte(a, b); }
+	
+	public void setShort(String a, short b) throws SQLException
+	{ inner.setShort(a, b); }
+	
+	public void setInt(String a, int b) throws SQLException
+	{ inner.setInt(a, b); }
+	
+	public void setLong(String a, long b) throws SQLException
+	{ inner.setLong(a, b); }
+	
+	public void setFloat(String a, float b) throws SQLException
+	{ inner.setFloat(a, b); }
+	
+	public void setDouble(String a, double b) throws SQLException
+	{ inner.setDouble(a, b); }
+	
+	public String getString(String a) throws SQLException
+	{ return inner.getString(a); }
+	
+	public String getString(int a) throws SQLException
+	{ return inner.getString(a); }
+	
+	public Ref getRef(int a) throws SQLException
+	{ return inner.getRef(a); }
+	
+	public Ref getRef(String a) throws SQLException
+	{ return inner.getRef(a); }
+	
+	public void setURL(String a, URL b) throws SQLException
+	{ inner.setURL(a, b); }
+	
+	public void setTime(String a, Time b) throws SQLException
+	{ inner.setTime(a, b); }
+	
+	public void setTime(String a, Time b, Calendar c) throws SQLException
+	{ inner.setTime(a, b, c); }
+	
+	public Time getTime(int a, Calendar b) throws SQLException
+	{ return inner.getTime(a, b); }
+	
+	public Time getTime(String a) throws SQLException
+	{ return inner.getTime(a); }
+	
+	public Time getTime(int a) throws SQLException
+	{ return inner.getTime(a); }
+	
+	public Time getTime(String a, Calendar b) throws SQLException
+	{ return inner.getTime(a, b); }
+	
+	public Date getDate(int a, Calendar b) throws SQLException
+	{ return inner.getDate(a, b); }
+	
+	public Date getDate(String a) throws SQLException
+	{ return inner.getDate(a); }
+	
+	public Date getDate(int a) throws SQLException
+	{ return inner.getDate(a); }
+	
+	public Date getDate(String a, Calendar b) throws SQLException
+	{ return inner.getDate(a, b); }
+	
+	public void setString(String a, String b) throws SQLException
+	{ inner.setString(a, b); }
+	
+	public Array getArray(int a) throws SQLException
+	{ return inner.getArray(a); }
+	
+	public Array getArray(String a) throws SQLException
+	{ return inner.getArray(a); }
+	
+	public void setDate(String a, Date b, Calendar c) throws SQLException
+	{ inner.setDate(a, b, c); }
+	
+	public void setDate(String a, Date b) throws SQLException
+	{ inner.setDate(a, b); }
+	
+	public ResultSetMetaData getMetaData() throws SQLException
+	{ return inner.getMetaData(); }
+	
+	public ResultSet executeQuery() throws SQLException
+	{ return inner.executeQuery(); }
+	
+	public int executeUpdate() throws SQLException
+	{ return inner.executeUpdate(); }
+	
+	public void addBatch() throws SQLException
+	{ inner.addBatch(); }
+	
+	public void setNull(int a, int b, String c) throws SQLException
+	{ inner.setNull(a, b, c); }
+	
+	public void setNull(int a, int b) throws SQLException
+	{ inner.setNull(a, b); }
+	
+	public void setBigDecimal(int a, BigDecimal b) throws SQLException
+	{ inner.setBigDecimal(a, b); }
+	
+	public void setBytes(int a, byte[] b) throws SQLException
+	{ inner.setBytes(a, b); }
+	
+	public void setTimestamp(int a, Timestamp b, Calendar c) throws SQLException
+	{ inner.setTimestamp(a, b, c); }
+	
+	public void setTimestamp(int a, Timestamp b) throws SQLException
+	{ inner.setTimestamp(a, b); }
+	
+	public void setAsciiStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setAsciiStream(a, b, c); }
+	
+	public void setUnicodeStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setUnicodeStream(a, b, c); }
+	
+	public void setBinaryStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setBinaryStream(a, b, c); }
+	
+	public void clearParameters() throws SQLException
+	{ inner.clearParameters(); }
+	
+	public void setObject(int a, Object b) throws SQLException
+	{ inner.setObject(a, b); }
+	
+	public void setObject(int a, Object b, int c, int d) throws SQLException
+	{ inner.setObject(a, b, c, d); }
+	
+	public void setObject(int a, Object b, int c) throws SQLException
+	{ inner.setObject(a, b, c); }
+	
+	public void setCharacterStream(int a, Reader b, int c) throws SQLException
+	{ inner.setCharacterStream(a, b, c); }
+	
+	public void setRef(int a, Ref b) throws SQLException
+	{ inner.setRef(a, b); }
+	
+	public void setBlob(int a, Blob b) throws SQLException
+	{ inner.setBlob(a, b); }
+	
+	public void setClob(int a, Clob b) throws SQLException
+	{ inner.setClob(a, b); }
+	
+	public void setArray(int a, Array b) throws SQLException
+	{ inner.setArray(a, b); }
+	
+	public ParameterMetaData getParameterMetaData() throws SQLException
+	{ return inner.getParameterMetaData(); }
+	
+	public void setBoolean(int a, boolean b) throws SQLException
+	{ inner.setBoolean(a, b); }
+	
+	public void setByte(int a, byte b) throws SQLException
+	{ inner.setByte(a, b); }
+	
+	public void setShort(int a, short b) throws SQLException
+	{ inner.setShort(a, b); }
+	
+	public void setInt(int a, int b) throws SQLException
+	{ inner.setInt(a, b); }
+	
+	public void setLong(int a, long b) throws SQLException
+	{ inner.setLong(a, b); }
+	
+	public void setFloat(int a, float b) throws SQLException
+	{ inner.setFloat(a, b); }
+	
+	public void setDouble(int a, double b) throws SQLException
+	{ inner.setDouble(a, b); }
+	
+	public void setURL(int a, URL b) throws SQLException
+	{ inner.setURL(a, b); }
+	
+	public void setTime(int a, Time b) throws SQLException
+	{ inner.setTime(a, b); }
+	
+	public void setTime(int a, Time b, Calendar c) throws SQLException
+	{ inner.setTime(a, b, c); }
+	
+	public boolean execute() throws SQLException
+	{ return inner.execute(); }
+	
+	public void setString(int a, String b) throws SQLException
+	{ inner.setString(a, b); }
+	
+	public void setDate(int a, Date b, Calendar c) throws SQLException
+	{ inner.setDate(a, b, c); }
+	
+	public void setDate(int a, Date b) throws SQLException
+	{ inner.setDate(a, b); }
+	
+	public SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public void setFetchDirection(int a) throws SQLException
+	{ inner.setFetchDirection(a); }
+	
+	public int getFetchDirection() throws SQLException
+	{ return inner.getFetchDirection(); }
+	
+	public void setFetchSize(int a) throws SQLException
+	{ inner.setFetchSize(a); }
+	
+	public int getFetchSize() throws SQLException
+	{ return inner.getFetchSize(); }
+	
+	public int getResultSetHoldability() throws SQLException
+	{ return inner.getResultSetHoldability(); }
+	
+	public ResultSet executeQuery(String a) throws SQLException
+	{ return inner.executeQuery(a); }
+	
+	public int executeUpdate(String a, int b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public int executeUpdate(String a, String[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public int executeUpdate(String a, int[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public int executeUpdate(String a) throws SQLException
+	{ return inner.executeUpdate(a); }
+	
+	public int getMaxFieldSize() throws SQLException
+	{ return inner.getMaxFieldSize(); }
+	
+	public void setMaxFieldSize(int a) throws SQLException
+	{ inner.setMaxFieldSize(a); }
+	
+	public int getMaxRows() throws SQLException
+	{ return inner.getMaxRows(); }
+	
+	public void setMaxRows(int a) throws SQLException
+	{ inner.setMaxRows(a); }
+	
+	public void setEscapeProcessing(boolean a) throws SQLException
+	{ inner.setEscapeProcessing(a); }
+	
+	public int getQueryTimeout() throws SQLException
+	{ return inner.getQueryTimeout(); }
+	
+	public void setQueryTimeout(int a) throws SQLException
+	{ inner.setQueryTimeout(a); }
+	
+	public void setCursorName(String a) throws SQLException
+	{ inner.setCursorName(a); }
+	
+	public ResultSet getResultSet() throws SQLException
+	{ return inner.getResultSet(); }
+	
+	public int getUpdateCount() throws SQLException
+	{ return inner.getUpdateCount(); }
+	
+	public boolean getMoreResults() throws SQLException
+	{ return inner.getMoreResults(); }
+	
+	public boolean getMoreResults(int a) throws SQLException
+	{ return inner.getMoreResults(a); }
+	
+	public int getResultSetConcurrency() throws SQLException
+	{ return inner.getResultSetConcurrency(); }
+	
+	public int getResultSetType() throws SQLException
+	{ return inner.getResultSetType(); }
+	
+	public void addBatch(String a) throws SQLException
+	{ inner.addBatch(a); }
+	
+	public void clearBatch() throws SQLException
+	{ inner.clearBatch(); }
+	
+	public int[] executeBatch() throws SQLException
+	{ return inner.executeBatch(); }
+	
+	public ResultSet getGeneratedKeys() throws SQLException
+	{ return inner.getGeneratedKeys(); }
+	
+	public void close() throws SQLException
+	{ inner.close(); }
+	
+	public boolean execute(String a, int b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public boolean execute(String a) throws SQLException
+	{ return inner.execute(a); }
+	
+	public boolean execute(String a, int[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public boolean execute(String a, String[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public Connection getConnection() throws SQLException
+	{ return inner.getConnection(); }
+	
+	public void cancel() throws SQLException
+	{ inner.cancel(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/FilterConnection.java b/src/classes/com/mchange/v2/sql/filter/FilterConnection.java
new file mode 100644
index 0000000..b470cc0
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/FilterConnection.java
@@ -0,0 +1,160 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.lang.String;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.util.Map;
+
+public abstract class FilterConnection implements Connection
+{
+	protected Connection inner;
+	
+	public FilterConnection(Connection inner)
+	{ this.inner = inner; }
+	
+	public FilterConnection()
+	{}
+	
+	public void setInner( Connection inner )
+	{ this.inner = inner; }
+	
+	public Connection getInner()
+	{ return inner; }
+	
+	public Statement createStatement(int a, int b, int c) throws SQLException
+	{ return inner.createStatement(a, b, c); }
+	
+	public Statement createStatement(int a, int b) throws SQLException
+	{ return inner.createStatement(a, b); }
+	
+	public Statement createStatement() throws SQLException
+	{ return inner.createStatement(); }
+	
+	public PreparedStatement prepareStatement(String a, String[] b) throws SQLException
+	{ return inner.prepareStatement(a, b); }
+	
+	public PreparedStatement prepareStatement(String a) throws SQLException
+	{ return inner.prepareStatement(a); }
+	
+	public PreparedStatement prepareStatement(String a, int b, int c) throws SQLException
+	{ return inner.prepareStatement(a, b, c); }
+	
+	public PreparedStatement prepareStatement(String a, int b, int c, int d) throws SQLException
+	{ return inner.prepareStatement(a, b, c, d); }
+	
+	public PreparedStatement prepareStatement(String a, int b) throws SQLException
+	{ return inner.prepareStatement(a, b); }
+	
+	public PreparedStatement prepareStatement(String a, int[] b) throws SQLException
+	{ return inner.prepareStatement(a, b); }
+	
+	public CallableStatement prepareCall(String a, int b, int c, int d) throws SQLException
+	{ return inner.prepareCall(a, b, c, d); }
+	
+	public CallableStatement prepareCall(String a, int b, int c) throws SQLException
+	{ return inner.prepareCall(a, b, c); }
+	
+	public CallableStatement prepareCall(String a) throws SQLException
+	{ return inner.prepareCall(a); }
+	
+	public String nativeSQL(String a) throws SQLException
+	{ return inner.nativeSQL(a); }
+	
+	public void setAutoCommit(boolean a) throws SQLException
+	{ inner.setAutoCommit(a); }
+	
+	public boolean getAutoCommit() throws SQLException
+	{ return inner.getAutoCommit(); }
+	
+	public void commit() throws SQLException
+	{ inner.commit(); }
+	
+	public void rollback(Savepoint a) throws SQLException
+	{ inner.rollback(a); }
+	
+	public void rollback() throws SQLException
+	{ inner.rollback(); }
+	
+	public DatabaseMetaData getMetaData() throws SQLException
+	{ return inner.getMetaData(); }
+	
+	public void setCatalog(String a) throws SQLException
+	{ inner.setCatalog(a); }
+	
+	public String getCatalog() throws SQLException
+	{ return inner.getCatalog(); }
+	
+	public void setTransactionIsolation(int a) throws SQLException
+	{ inner.setTransactionIsolation(a); }
+	
+	public int getTransactionIsolation() throws SQLException
+	{ return inner.getTransactionIsolation(); }
+	
+	public SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public Map getTypeMap() throws SQLException
+	{ return inner.getTypeMap(); }
+	
+	public void setTypeMap(Map a) throws SQLException
+	{ inner.setTypeMap(a); }
+	
+	public void setHoldability(int a) throws SQLException
+	{ inner.setHoldability(a); }
+	
+	public int getHoldability() throws SQLException
+	{ return inner.getHoldability(); }
+	
+	public Savepoint setSavepoint() throws SQLException
+	{ return inner.setSavepoint(); }
+	
+	public Savepoint setSavepoint(String a) throws SQLException
+	{ return inner.setSavepoint(a); }
+	
+	public void releaseSavepoint(Savepoint a) throws SQLException
+	{ inner.releaseSavepoint(a); }
+	
+	public void setReadOnly(boolean a) throws SQLException
+	{ inner.setReadOnly(a); }
+	
+	public boolean isReadOnly() throws SQLException
+	{ return inner.isReadOnly(); }
+	
+	public void close() throws SQLException
+	{ inner.close(); }
+	
+	public boolean isClosed() throws SQLException
+	{ return inner.isClosed(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/FilterDatabaseMetaData.java b/src/classes/com/mchange/v2/sql/filter/FilterDatabaseMetaData.java
new file mode 100644
index 0000000..3b8d000
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/FilterDatabaseMetaData.java
@@ -0,0 +1,542 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.lang.String;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public abstract class FilterDatabaseMetaData implements DatabaseMetaData
+{
+	protected DatabaseMetaData inner;
+	
+	public FilterDatabaseMetaData(DatabaseMetaData inner)
+	{ this.inner = inner; }
+	
+	public FilterDatabaseMetaData()
+	{}
+	
+	public void setInner( DatabaseMetaData inner )
+	{ this.inner = inner; }
+	
+	public DatabaseMetaData getInner()
+	{ return inner; }
+	
+	public boolean allProceduresAreCallable() throws SQLException
+	{ return inner.allProceduresAreCallable(); }
+	
+	public boolean allTablesAreSelectable() throws SQLException
+	{ return inner.allTablesAreSelectable(); }
+	
+	public boolean nullsAreSortedHigh() throws SQLException
+	{ return inner.nullsAreSortedHigh(); }
+	
+	public boolean nullsAreSortedLow() throws SQLException
+	{ return inner.nullsAreSortedLow(); }
+	
+	public boolean nullsAreSortedAtStart() throws SQLException
+	{ return inner.nullsAreSortedAtStart(); }
+	
+	public boolean nullsAreSortedAtEnd() throws SQLException
+	{ return inner.nullsAreSortedAtEnd(); }
+	
+	public String getDatabaseProductName() throws SQLException
+	{ return inner.getDatabaseProductName(); }
+	
+	public String getDatabaseProductVersion() throws SQLException
+	{ return inner.getDatabaseProductVersion(); }
+	
+	public String getDriverName() throws SQLException
+	{ return inner.getDriverName(); }
+	
+	public String getDriverVersion() throws SQLException
+	{ return inner.getDriverVersion(); }
+	
+	public int getDriverMajorVersion()
+	{ return inner.getDriverMajorVersion(); }
+	
+	public int getDriverMinorVersion()
+	{ return inner.getDriverMinorVersion(); }
+	
+	public boolean usesLocalFiles() throws SQLException
+	{ return inner.usesLocalFiles(); }
+	
+	public boolean usesLocalFilePerTable() throws SQLException
+	{ return inner.usesLocalFilePerTable(); }
+	
+	public boolean supportsMixedCaseIdentifiers() throws SQLException
+	{ return inner.supportsMixedCaseIdentifiers(); }
+	
+	public boolean storesUpperCaseIdentifiers() throws SQLException
+	{ return inner.storesUpperCaseIdentifiers(); }
+	
+	public boolean storesLowerCaseIdentifiers() throws SQLException
+	{ return inner.storesLowerCaseIdentifiers(); }
+	
+	public boolean storesMixedCaseIdentifiers() throws SQLException
+	{ return inner.storesMixedCaseIdentifiers(); }
+	
+	public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException
+	{ return inner.supportsMixedCaseQuotedIdentifiers(); }
+	
+	public boolean storesUpperCaseQuotedIdentifiers() throws SQLException
+	{ return inner.storesUpperCaseQuotedIdentifiers(); }
+	
+	public boolean storesLowerCaseQuotedIdentifiers() throws SQLException
+	{ return inner.storesLowerCaseQuotedIdentifiers(); }
+	
+	public boolean storesMixedCaseQuotedIdentifiers() throws SQLException
+	{ return inner.storesMixedCaseQuotedIdentifiers(); }
+	
+	public String getIdentifierQuoteString() throws SQLException
+	{ return inner.getIdentifierQuoteString(); }
+	
+	public String getSQLKeywords() throws SQLException
+	{ return inner.getSQLKeywords(); }
+	
+	public String getNumericFunctions() throws SQLException
+	{ return inner.getNumericFunctions(); }
+	
+	public String getStringFunctions() throws SQLException
+	{ return inner.getStringFunctions(); }
+	
+	public String getSystemFunctions() throws SQLException
+	{ return inner.getSystemFunctions(); }
+	
+	public String getTimeDateFunctions() throws SQLException
+	{ return inner.getTimeDateFunctions(); }
+	
+	public String getSearchStringEscape() throws SQLException
+	{ return inner.getSearchStringEscape(); }
+	
+	public String getExtraNameCharacters() throws SQLException
+	{ return inner.getExtraNameCharacters(); }
+	
+	public boolean supportsAlterTableWithAddColumn() throws SQLException
+	{ return inner.supportsAlterTableWithAddColumn(); }
+	
+	public boolean supportsAlterTableWithDropColumn() throws SQLException
+	{ return inner.supportsAlterTableWithDropColumn(); }
+	
+	public boolean supportsColumnAliasing() throws SQLException
+	{ return inner.supportsColumnAliasing(); }
+	
+	public boolean nullPlusNonNullIsNull() throws SQLException
+	{ return inner.nullPlusNonNullIsNull(); }
+	
+	public boolean supportsConvert() throws SQLException
+	{ return inner.supportsConvert(); }
+	
+	public boolean supportsConvert(int a, int b) throws SQLException
+	{ return inner.supportsConvert(a, b); }
+	
+	public boolean supportsTableCorrelationNames() throws SQLException
+	{ return inner.supportsTableCorrelationNames(); }
+	
+	public boolean supportsDifferentTableCorrelationNames() throws SQLException
+	{ return inner.supportsDifferentTableCorrelationNames(); }
+	
+	public boolean supportsExpressionsInOrderBy() throws SQLException
+	{ return inner.supportsExpressionsInOrderBy(); }
+	
+	public boolean supportsOrderByUnrelated() throws SQLException
+	{ return inner.supportsOrderByUnrelated(); }
+	
+	public boolean supportsGroupBy() throws SQLException
+	{ return inner.supportsGroupBy(); }
+	
+	public boolean supportsGroupByUnrelated() throws SQLException
+	{ return inner.supportsGroupByUnrelated(); }
+	
+	public boolean supportsGroupByBeyondSelect() throws SQLException
+	{ return inner.supportsGroupByBeyondSelect(); }
+	
+	public boolean supportsLikeEscapeClause() throws SQLException
+	{ return inner.supportsLikeEscapeClause(); }
+	
+	public boolean supportsMultipleResultSets() throws SQLException
+	{ return inner.supportsMultipleResultSets(); }
+	
+	public boolean supportsMultipleTransactions() throws SQLException
+	{ return inner.supportsMultipleTransactions(); }
+	
+	public boolean supportsNonNullableColumns() throws SQLException
+	{ return inner.supportsNonNullableColumns(); }
+	
+	public boolean supportsMinimumSQLGrammar() throws SQLException
+	{ return inner.supportsMinimumSQLGrammar(); }
+	
+	public boolean supportsCoreSQLGrammar() throws SQLException
+	{ return inner.supportsCoreSQLGrammar(); }
+	
+	public boolean supportsExtendedSQLGrammar() throws SQLException
+	{ return inner.supportsExtendedSQLGrammar(); }
+	
+	public boolean supportsANSI92EntryLevelSQL() throws SQLException
+	{ return inner.supportsANSI92EntryLevelSQL(); }
+	
+	public boolean supportsANSI92IntermediateSQL() throws SQLException
+	{ return inner.supportsANSI92IntermediateSQL(); }
+	
+	public boolean supportsANSI92FullSQL() throws SQLException
+	{ return inner.supportsANSI92FullSQL(); }
+	
+	public boolean supportsIntegrityEnhancementFacility() throws SQLException
+	{ return inner.supportsIntegrityEnhancementFacility(); }
+	
+	public boolean supportsOuterJoins() throws SQLException
+	{ return inner.supportsOuterJoins(); }
+	
+	public boolean supportsFullOuterJoins() throws SQLException
+	{ return inner.supportsFullOuterJoins(); }
+	
+	public boolean supportsLimitedOuterJoins() throws SQLException
+	{ return inner.supportsLimitedOuterJoins(); }
+	
+	public String getSchemaTerm() throws SQLException
+	{ return inner.getSchemaTerm(); }
+	
+	public String getProcedureTerm() throws SQLException
+	{ return inner.getProcedureTerm(); }
+	
+	public String getCatalogTerm() throws SQLException
+	{ return inner.getCatalogTerm(); }
+	
+	public boolean isCatalogAtStart() throws SQLException
+	{ return inner.isCatalogAtStart(); }
+	
+	public String getCatalogSeparator() throws SQLException
+	{ return inner.getCatalogSeparator(); }
+	
+	public boolean supportsSchemasInDataManipulation() throws SQLException
+	{ return inner.supportsSchemasInDataManipulation(); }
+	
+	public boolean supportsSchemasInProcedureCalls() throws SQLException
+	{ return inner.supportsSchemasInProcedureCalls(); }
+	
+	public boolean supportsSchemasInTableDefinitions() throws SQLException
+	{ return inner.supportsSchemasInTableDefinitions(); }
+	
+	public boolean supportsSchemasInIndexDefinitions() throws SQLException
+	{ return inner.supportsSchemasInIndexDefinitions(); }
+	
+	public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException
+	{ return inner.supportsSchemasInPrivilegeDefinitions(); }
+	
+	public boolean supportsCatalogsInDataManipulation() throws SQLException
+	{ return inner.supportsCatalogsInDataManipulation(); }
+	
+	public boolean supportsCatalogsInProcedureCalls() throws SQLException
+	{ return inner.supportsCatalogsInProcedureCalls(); }
+	
+	public boolean supportsCatalogsInTableDefinitions() throws SQLException
+	{ return inner.supportsCatalogsInTableDefinitions(); }
+	
+	public boolean supportsCatalogsInIndexDefinitions() throws SQLException
+	{ return inner.supportsCatalogsInIndexDefinitions(); }
+	
+	public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException
+	{ return inner.supportsCatalogsInPrivilegeDefinitions(); }
+	
+	public boolean supportsPositionedDelete() throws SQLException
+	{ return inner.supportsPositionedDelete(); }
+	
+	public boolean supportsPositionedUpdate() throws SQLException
+	{ return inner.supportsPositionedUpdate(); }
+	
+	public boolean supportsSelectForUpdate() throws SQLException
+	{ return inner.supportsSelectForUpdate(); }
+	
+	public boolean supportsStoredProcedures() throws SQLException
+	{ return inner.supportsStoredProcedures(); }
+	
+	public boolean supportsSubqueriesInComparisons() throws SQLException
+	{ return inner.supportsSubqueriesInComparisons(); }
+	
+	public boolean supportsSubqueriesInExists() throws SQLException
+	{ return inner.supportsSubqueriesInExists(); }
+	
+	public boolean supportsSubqueriesInIns() throws SQLException
+	{ return inner.supportsSubqueriesInIns(); }
+	
+	public boolean supportsSubqueriesInQuantifieds() throws SQLException
+	{ return inner.supportsSubqueriesInQuantifieds(); }
+	
+	public boolean supportsCorrelatedSubqueries() throws SQLException
+	{ return inner.supportsCorrelatedSubqueries(); }
+	
+	public boolean supportsUnion() throws SQLException
+	{ return inner.supportsUnion(); }
+	
+	public boolean supportsUnionAll() throws SQLException
+	{ return inner.supportsUnionAll(); }
+	
+	public boolean supportsOpenCursorsAcrossCommit() throws SQLException
+	{ return inner.supportsOpenCursorsAcrossCommit(); }
+	
+	public boolean supportsOpenCursorsAcrossRollback() throws SQLException
+	{ return inner.supportsOpenCursorsAcrossRollback(); }
+	
+	public boolean supportsOpenStatementsAcrossCommit() throws SQLException
+	{ return inner.supportsOpenStatementsAcrossCommit(); }
+	
+	public boolean supportsOpenStatementsAcrossRollback() throws SQLException
+	{ return inner.supportsOpenStatementsAcrossRollback(); }
+	
+	public int getMaxBinaryLiteralLength() throws SQLException
+	{ return inner.getMaxBinaryLiteralLength(); }
+	
+	public int getMaxCharLiteralLength() throws SQLException
+	{ return inner.getMaxCharLiteralLength(); }
+	
+	public int getMaxColumnNameLength() throws SQLException
+	{ return inner.getMaxColumnNameLength(); }
+	
+	public int getMaxColumnsInGroupBy() throws SQLException
+	{ return inner.getMaxColumnsInGroupBy(); }
+	
+	public int getMaxColumnsInIndex() throws SQLException
+	{ return inner.getMaxColumnsInIndex(); }
+	
+	public int getMaxColumnsInOrderBy() throws SQLException
+	{ return inner.getMaxColumnsInOrderBy(); }
+	
+	public int getMaxColumnsInSelect() throws SQLException
+	{ return inner.getMaxColumnsInSelect(); }
+	
+	public int getMaxColumnsInTable() throws SQLException
+	{ return inner.getMaxColumnsInTable(); }
+	
+	public int getMaxConnections() throws SQLException
+	{ return inner.getMaxConnections(); }
+	
+	public int getMaxCursorNameLength() throws SQLException
+	{ return inner.getMaxCursorNameLength(); }
+	
+	public int getMaxIndexLength() throws SQLException
+	{ return inner.getMaxIndexLength(); }
+	
+	public int getMaxSchemaNameLength() throws SQLException
+	{ return inner.getMaxSchemaNameLength(); }
+	
+	public int getMaxProcedureNameLength() throws SQLException
+	{ return inner.getMaxProcedureNameLength(); }
+	
+	public int getMaxCatalogNameLength() throws SQLException
+	{ return inner.getMaxCatalogNameLength(); }
+	
+	public int getMaxRowSize() throws SQLException
+	{ return inner.getMaxRowSize(); }
+	
+	public boolean doesMaxRowSizeIncludeBlobs() throws SQLException
+	{ return inner.doesMaxRowSizeIncludeBlobs(); }
+	
+	public int getMaxStatementLength() throws SQLException
+	{ return inner.getMaxStatementLength(); }
+	
+	public int getMaxStatements() throws SQLException
+	{ return inner.getMaxStatements(); }
+	
+	public int getMaxTableNameLength() throws SQLException
+	{ return inner.getMaxTableNameLength(); }
+	
+	public int getMaxTablesInSelect() throws SQLException
+	{ return inner.getMaxTablesInSelect(); }
+	
+	public int getMaxUserNameLength() throws SQLException
+	{ return inner.getMaxUserNameLength(); }
+	
+	public int getDefaultTransactionIsolation() throws SQLException
+	{ return inner.getDefaultTransactionIsolation(); }
+	
+	public boolean supportsTransactions() throws SQLException
+	{ return inner.supportsTransactions(); }
+	
+	public boolean supportsTransactionIsolationLevel(int a) throws SQLException
+	{ return inner.supportsTransactionIsolationLevel(a); }
+	
+	public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException
+	{ return inner.supportsDataDefinitionAndDataManipulationTransactions(); }
+	
+	public boolean supportsDataManipulationTransactionsOnly() throws SQLException
+	{ return inner.supportsDataManipulationTransactionsOnly(); }
+	
+	public boolean dataDefinitionCausesTransactionCommit() throws SQLException
+	{ return inner.dataDefinitionCausesTransactionCommit(); }
+	
+	public boolean dataDefinitionIgnoredInTransactions() throws SQLException
+	{ return inner.dataDefinitionIgnoredInTransactions(); }
+	
+	public ResultSet getProcedures(String a, String b, String c) throws SQLException
+	{ return inner.getProcedures(a, b, c); }
+	
+	public ResultSet getProcedureColumns(String a, String b, String c, String d) throws SQLException
+	{ return inner.getProcedureColumns(a, b, c, d); }
+	
+	public ResultSet getTables(String a, String b, String c, String[] d) throws SQLException
+	{ return inner.getTables(a, b, c, d); }
+	
+	public ResultSet getSchemas() throws SQLException
+	{ return inner.getSchemas(); }
+	
+	public ResultSet getCatalogs() throws SQLException
+	{ return inner.getCatalogs(); }
+	
+	public ResultSet getTableTypes() throws SQLException
+	{ return inner.getTableTypes(); }
+	
+	public ResultSet getColumnPrivileges(String a, String b, String c, String d) throws SQLException
+	{ return inner.getColumnPrivileges(a, b, c, d); }
+	
+	public ResultSet getTablePrivileges(String a, String b, String c) throws SQLException
+	{ return inner.getTablePrivileges(a, b, c); }
+	
+	public ResultSet getBestRowIdentifier(String a, String b, String c, int d, boolean e) throws SQLException
+	{ return inner.getBestRowIdentifier(a, b, c, d, e); }
+	
+	public ResultSet getVersionColumns(String a, String b, String c) throws SQLException
+	{ return inner.getVersionColumns(a, b, c); }
+	
+	public ResultSet getPrimaryKeys(String a, String b, String c) throws SQLException
+	{ return inner.getPrimaryKeys(a, b, c); }
+	
+	public ResultSet getImportedKeys(String a, String b, String c) throws SQLException
+	{ return inner.getImportedKeys(a, b, c); }
+	
+	public ResultSet getExportedKeys(String a, String b, String c) throws SQLException
+	{ return inner.getExportedKeys(a, b, c); }
+	
+	public ResultSet getCrossReference(String a, String b, String c, String d, String e, String f) throws SQLException
+	{ return inner.getCrossReference(a, b, c, d, e, f); }
+	
+	public ResultSet getTypeInfo() throws SQLException
+	{ return inner.getTypeInfo(); }
+	
+	public ResultSet getIndexInfo(String a, String b, String c, boolean d, boolean e) throws SQLException
+	{ return inner.getIndexInfo(a, b, c, d, e); }
+	
+	public boolean supportsResultSetType(int a) throws SQLException
+	{ return inner.supportsResultSetType(a); }
+	
+	public boolean supportsResultSetConcurrency(int a, int b) throws SQLException
+	{ return inner.supportsResultSetConcurrency(a, b); }
+	
+	public boolean ownUpdatesAreVisible(int a) throws SQLException
+	{ return inner.ownUpdatesAreVisible(a); }
+	
+	public boolean ownDeletesAreVisible(int a) throws SQLException
+	{ return inner.ownDeletesAreVisible(a); }
+	
+	public boolean ownInsertsAreVisible(int a) throws SQLException
+	{ return inner.ownInsertsAreVisible(a); }
+	
+	public boolean othersUpdatesAreVisible(int a) throws SQLException
+	{ return inner.othersUpdatesAreVisible(a); }
+	
+	public boolean othersDeletesAreVisible(int a) throws SQLException
+	{ return inner.othersDeletesAreVisible(a); }
+	
+	public boolean othersInsertsAreVisible(int a) throws SQLException
+	{ return inner.othersInsertsAreVisible(a); }
+	
+	public boolean updatesAreDetected(int a) throws SQLException
+	{ return inner.updatesAreDetected(a); }
+	
+	public boolean deletesAreDetected(int a) throws SQLException
+	{ return inner.deletesAreDetected(a); }
+	
+	public boolean insertsAreDetected(int a) throws SQLException
+	{ return inner.insertsAreDetected(a); }
+	
+	public boolean supportsBatchUpdates() throws SQLException
+	{ return inner.supportsBatchUpdates(); }
+	
+	public ResultSet getUDTs(String a, String b, String c, int[] d) throws SQLException
+	{ return inner.getUDTs(a, b, c, d); }
+	
+	public boolean supportsSavepoints() throws SQLException
+	{ return inner.supportsSavepoints(); }
+	
+	public boolean supportsNamedParameters() throws SQLException
+	{ return inner.supportsNamedParameters(); }
+	
+	public boolean supportsMultipleOpenResults() throws SQLException
+	{ return inner.supportsMultipleOpenResults(); }
+	
+	public boolean supportsGetGeneratedKeys() throws SQLException
+	{ return inner.supportsGetGeneratedKeys(); }
+	
+	public ResultSet getSuperTypes(String a, String b, String c) throws SQLException
+	{ return inner.getSuperTypes(a, b, c); }
+	
+	public ResultSet getSuperTables(String a, String b, String c) throws SQLException
+	{ return inner.getSuperTables(a, b, c); }
+	
+	public boolean supportsResultSetHoldability(int a) throws SQLException
+	{ return inner.supportsResultSetHoldability(a); }
+	
+	public int getResultSetHoldability() throws SQLException
+	{ return inner.getResultSetHoldability(); }
+	
+	public int getDatabaseMajorVersion() throws SQLException
+	{ return inner.getDatabaseMajorVersion(); }
+	
+	public int getDatabaseMinorVersion() throws SQLException
+	{ return inner.getDatabaseMinorVersion(); }
+	
+	public int getJDBCMajorVersion() throws SQLException
+	{ return inner.getJDBCMajorVersion(); }
+	
+	public int getJDBCMinorVersion() throws SQLException
+	{ return inner.getJDBCMinorVersion(); }
+	
+	public int getSQLStateType() throws SQLException
+	{ return inner.getSQLStateType(); }
+	
+	public boolean locatorsUpdateCopy() throws SQLException
+	{ return inner.locatorsUpdateCopy(); }
+	
+	public boolean supportsStatementPooling() throws SQLException
+	{ return inner.supportsStatementPooling(); }
+	
+	public String getURL() throws SQLException
+	{ return inner.getURL(); }
+	
+	public boolean isReadOnly() throws SQLException
+	{ return inner.isReadOnly(); }
+	
+	public ResultSet getAttributes(String a, String b, String c, String d) throws SQLException
+	{ return inner.getAttributes(a, b, c, d); }
+	
+	public Connection getConnection() throws SQLException
+	{ return inner.getConnection(); }
+	
+	public ResultSet getColumns(String a, String b, String c, String d) throws SQLException
+	{ return inner.getColumns(a, b, c, d); }
+	
+	public String getUserName() throws SQLException
+	{ return inner.getUserName(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/FilterPreparedStatement.java b/src/classes/com/mchange/v2/sql/filter/FilterPreparedStatement.java
new file mode 100644
index 0000000..1d8ff68
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/FilterPreparedStatement.java
@@ -0,0 +1,285 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.Object;
+import java.lang.String;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+
+public abstract class FilterPreparedStatement implements PreparedStatement
+{
+	protected PreparedStatement inner;
+	
+	public FilterPreparedStatement(PreparedStatement inner)
+	{ this.inner = inner; }
+	
+	public FilterPreparedStatement()
+	{}
+	
+	public void setInner( PreparedStatement inner )
+	{ this.inner = inner; }
+	
+	public PreparedStatement getInner()
+	{ return inner; }
+	
+	public ResultSetMetaData getMetaData() throws SQLException
+	{ return inner.getMetaData(); }
+	
+	public ResultSet executeQuery() throws SQLException
+	{ return inner.executeQuery(); }
+	
+	public int executeUpdate() throws SQLException
+	{ return inner.executeUpdate(); }
+	
+	public void addBatch() throws SQLException
+	{ inner.addBatch(); }
+	
+	public void setNull(int a, int b, String c) throws SQLException
+	{ inner.setNull(a, b, c); }
+	
+	public void setNull(int a, int b) throws SQLException
+	{ inner.setNull(a, b); }
+	
+	public void setBigDecimal(int a, BigDecimal b) throws SQLException
+	{ inner.setBigDecimal(a, b); }
+	
+	public void setBytes(int a, byte[] b) throws SQLException
+	{ inner.setBytes(a, b); }
+	
+	public void setTimestamp(int a, Timestamp b, Calendar c) throws SQLException
+	{ inner.setTimestamp(a, b, c); }
+	
+	public void setTimestamp(int a, Timestamp b) throws SQLException
+	{ inner.setTimestamp(a, b); }
+	
+	public void setAsciiStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setAsciiStream(a, b, c); }
+	
+	public void setUnicodeStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setUnicodeStream(a, b, c); }
+	
+	public void setBinaryStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setBinaryStream(a, b, c); }
+	
+	public void clearParameters() throws SQLException
+	{ inner.clearParameters(); }
+	
+	public void setObject(int a, Object b) throws SQLException
+	{ inner.setObject(a, b); }
+	
+	public void setObject(int a, Object b, int c, int d) throws SQLException
+	{ inner.setObject(a, b, c, d); }
+	
+	public void setObject(int a, Object b, int c) throws SQLException
+	{ inner.setObject(a, b, c); }
+	
+	public void setCharacterStream(int a, Reader b, int c) throws SQLException
+	{ inner.setCharacterStream(a, b, c); }
+	
+	public void setRef(int a, Ref b) throws SQLException
+	{ inner.setRef(a, b); }
+	
+	public void setBlob(int a, Blob b) throws SQLException
+	{ inner.setBlob(a, b); }
+	
+	public void setClob(int a, Clob b) throws SQLException
+	{ inner.setClob(a, b); }
+	
+	public void setArray(int a, Array b) throws SQLException
+	{ inner.setArray(a, b); }
+	
+	public ParameterMetaData getParameterMetaData() throws SQLException
+	{ return inner.getParameterMetaData(); }
+	
+	public void setBoolean(int a, boolean b) throws SQLException
+	{ inner.setBoolean(a, b); }
+	
+	public void setByte(int a, byte b) throws SQLException
+	{ inner.setByte(a, b); }
+	
+	public void setShort(int a, short b) throws SQLException
+	{ inner.setShort(a, b); }
+	
+	public void setInt(int a, int b) throws SQLException
+	{ inner.setInt(a, b); }
+	
+	public void setLong(int a, long b) throws SQLException
+	{ inner.setLong(a, b); }
+	
+	public void setFloat(int a, float b) throws SQLException
+	{ inner.setFloat(a, b); }
+	
+	public void setDouble(int a, double b) throws SQLException
+	{ inner.setDouble(a, b); }
+	
+	public void setURL(int a, URL b) throws SQLException
+	{ inner.setURL(a, b); }
+	
+	public void setTime(int a, Time b) throws SQLException
+	{ inner.setTime(a, b); }
+	
+	public void setTime(int a, Time b, Calendar c) throws SQLException
+	{ inner.setTime(a, b, c); }
+	
+	public boolean execute() throws SQLException
+	{ return inner.execute(); }
+	
+	public void setString(int a, String b) throws SQLException
+	{ inner.setString(a, b); }
+	
+	public void setDate(int a, Date b, Calendar c) throws SQLException
+	{ inner.setDate(a, b, c); }
+	
+	public void setDate(int a, Date b) throws SQLException
+	{ inner.setDate(a, b); }
+	
+	public SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public void setFetchDirection(int a) throws SQLException
+	{ inner.setFetchDirection(a); }
+	
+	public int getFetchDirection() throws SQLException
+	{ return inner.getFetchDirection(); }
+	
+	public void setFetchSize(int a) throws SQLException
+	{ inner.setFetchSize(a); }
+	
+	public int getFetchSize() throws SQLException
+	{ return inner.getFetchSize(); }
+	
+	public int getResultSetHoldability() throws SQLException
+	{ return inner.getResultSetHoldability(); }
+	
+	public ResultSet executeQuery(String a) throws SQLException
+	{ return inner.executeQuery(a); }
+	
+	public int executeUpdate(String a, int b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public int executeUpdate(String a, String[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public int executeUpdate(String a, int[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public int executeUpdate(String a) throws SQLException
+	{ return inner.executeUpdate(a); }
+	
+	public int getMaxFieldSize() throws SQLException
+	{ return inner.getMaxFieldSize(); }
+	
+	public void setMaxFieldSize(int a) throws SQLException
+	{ inner.setMaxFieldSize(a); }
+	
+	public int getMaxRows() throws SQLException
+	{ return inner.getMaxRows(); }
+	
+	public void setMaxRows(int a) throws SQLException
+	{ inner.setMaxRows(a); }
+	
+	public void setEscapeProcessing(boolean a) throws SQLException
+	{ inner.setEscapeProcessing(a); }
+	
+	public int getQueryTimeout() throws SQLException
+	{ return inner.getQueryTimeout(); }
+	
+	public void setQueryTimeout(int a) throws SQLException
+	{ inner.setQueryTimeout(a); }
+	
+	public void setCursorName(String a) throws SQLException
+	{ inner.setCursorName(a); }
+	
+	public ResultSet getResultSet() throws SQLException
+	{ return inner.getResultSet(); }
+	
+	public int getUpdateCount() throws SQLException
+	{ return inner.getUpdateCount(); }
+	
+	public boolean getMoreResults() throws SQLException
+	{ return inner.getMoreResults(); }
+	
+	public boolean getMoreResults(int a) throws SQLException
+	{ return inner.getMoreResults(a); }
+	
+	public int getResultSetConcurrency() throws SQLException
+	{ return inner.getResultSetConcurrency(); }
+	
+	public int getResultSetType() throws SQLException
+	{ return inner.getResultSetType(); }
+	
+	public void addBatch(String a) throws SQLException
+	{ inner.addBatch(a); }
+	
+	public void clearBatch() throws SQLException
+	{ inner.clearBatch(); }
+	
+	public int[] executeBatch() throws SQLException
+	{ return inner.executeBatch(); }
+	
+	public ResultSet getGeneratedKeys() throws SQLException
+	{ return inner.getGeneratedKeys(); }
+	
+	public void close() throws SQLException
+	{ inner.close(); }
+	
+	public boolean execute(String a, int b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public boolean execute(String a) throws SQLException
+	{ return inner.execute(a); }
+	
+	public boolean execute(String a, int[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public boolean execute(String a, String[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public Connection getConnection() throws SQLException
+	{ return inner.getConnection(); }
+	
+	public void cancel() throws SQLException
+	{ inner.cancel(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/FilterResultSet.java b/src/classes/com/mchange/v2/sql/filter/FilterResultSet.java
new file mode 100644
index 0000000..fdb1777
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/FilterResultSet.java
@@ -0,0 +1,479 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.Object;
+import java.lang.String;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Map;
+
+public abstract class FilterResultSet implements ResultSet
+{
+	protected ResultSet inner;
+	
+	public FilterResultSet(ResultSet inner)
+	{ this.inner = inner; }
+	
+	public FilterResultSet()
+	{}
+	
+	public void setInner( ResultSet inner )
+	{ this.inner = inner; }
+	
+	public ResultSet getInner()
+	{ return inner; }
+	
+	public ResultSetMetaData getMetaData() throws SQLException
+	{ return inner.getMetaData(); }
+	
+	public SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public boolean wasNull() throws SQLException
+	{ return inner.wasNull(); }
+	
+	public BigDecimal getBigDecimal(int a) throws SQLException
+	{ return inner.getBigDecimal(a); }
+	
+	public BigDecimal getBigDecimal(String a, int b) throws SQLException
+	{ return inner.getBigDecimal(a, b); }
+	
+	public BigDecimal getBigDecimal(int a, int b) throws SQLException
+	{ return inner.getBigDecimal(a, b); }
+	
+	public BigDecimal getBigDecimal(String a) throws SQLException
+	{ return inner.getBigDecimal(a); }
+	
+	public Timestamp getTimestamp(int a) throws SQLException
+	{ return inner.getTimestamp(a); }
+	
+	public Timestamp getTimestamp(String a) throws SQLException
+	{ return inner.getTimestamp(a); }
+	
+	public Timestamp getTimestamp(int a, Calendar b) throws SQLException
+	{ return inner.getTimestamp(a, b); }
+	
+	public Timestamp getTimestamp(String a, Calendar b) throws SQLException
+	{ return inner.getTimestamp(a, b); }
+	
+	public InputStream getAsciiStream(String a) throws SQLException
+	{ return inner.getAsciiStream(a); }
+	
+	public InputStream getAsciiStream(int a) throws SQLException
+	{ return inner.getAsciiStream(a); }
+	
+	public InputStream getUnicodeStream(String a) throws SQLException
+	{ return inner.getUnicodeStream(a); }
+	
+	public InputStream getUnicodeStream(int a) throws SQLException
+	{ return inner.getUnicodeStream(a); }
+	
+	public InputStream getBinaryStream(int a) throws SQLException
+	{ return inner.getBinaryStream(a); }
+	
+	public InputStream getBinaryStream(String a) throws SQLException
+	{ return inner.getBinaryStream(a); }
+	
+	public String getCursorName() throws SQLException
+	{ return inner.getCursorName(); }
+	
+	public Reader getCharacterStream(int a) throws SQLException
+	{ return inner.getCharacterStream(a); }
+	
+	public Reader getCharacterStream(String a) throws SQLException
+	{ return inner.getCharacterStream(a); }
+	
+	public boolean isBeforeFirst() throws SQLException
+	{ return inner.isBeforeFirst(); }
+	
+	public boolean isAfterLast() throws SQLException
+	{ return inner.isAfterLast(); }
+	
+	public boolean isFirst() throws SQLException
+	{ return inner.isFirst(); }
+	
+	public boolean isLast() throws SQLException
+	{ return inner.isLast(); }
+	
+	public void beforeFirst() throws SQLException
+	{ inner.beforeFirst(); }
+	
+	public void afterLast() throws SQLException
+	{ inner.afterLast(); }
+	
+	public boolean absolute(int a) throws SQLException
+	{ return inner.absolute(a); }
+	
+	public void setFetchDirection(int a) throws SQLException
+	{ inner.setFetchDirection(a); }
+	
+	public int getFetchDirection() throws SQLException
+	{ return inner.getFetchDirection(); }
+	
+	public void setFetchSize(int a) throws SQLException
+	{ inner.setFetchSize(a); }
+	
+	public int getFetchSize() throws SQLException
+	{ return inner.getFetchSize(); }
+	
+	public int getConcurrency() throws SQLException
+	{ return inner.getConcurrency(); }
+	
+	public boolean rowUpdated() throws SQLException
+	{ return inner.rowUpdated(); }
+	
+	public boolean rowInserted() throws SQLException
+	{ return inner.rowInserted(); }
+	
+	public boolean rowDeleted() throws SQLException
+	{ return inner.rowDeleted(); }
+	
+	public void updateNull(int a) throws SQLException
+	{ inner.updateNull(a); }
+	
+	public void updateNull(String a) throws SQLException
+	{ inner.updateNull(a); }
+	
+	public void updateBoolean(int a, boolean b) throws SQLException
+	{ inner.updateBoolean(a, b); }
+	
+	public void updateBoolean(String a, boolean b) throws SQLException
+	{ inner.updateBoolean(a, b); }
+	
+	public void updateByte(int a, byte b) throws SQLException
+	{ inner.updateByte(a, b); }
+	
+	public void updateByte(String a, byte b) throws SQLException
+	{ inner.updateByte(a, b); }
+	
+	public void updateShort(int a, short b) throws SQLException
+	{ inner.updateShort(a, b); }
+	
+	public void updateShort(String a, short b) throws SQLException
+	{ inner.updateShort(a, b); }
+	
+	public void updateInt(String a, int b) throws SQLException
+	{ inner.updateInt(a, b); }
+	
+	public void updateInt(int a, int b) throws SQLException
+	{ inner.updateInt(a, b); }
+	
+	public void updateLong(int a, long b) throws SQLException
+	{ inner.updateLong(a, b); }
+	
+	public void updateLong(String a, long b) throws SQLException
+	{ inner.updateLong(a, b); }
+	
+	public void updateFloat(String a, float b) throws SQLException
+	{ inner.updateFloat(a, b); }
+	
+	public void updateFloat(int a, float b) throws SQLException
+	{ inner.updateFloat(a, b); }
+	
+	public void updateDouble(String a, double b) throws SQLException
+	{ inner.updateDouble(a, b); }
+	
+	public void updateDouble(int a, double b) throws SQLException
+	{ inner.updateDouble(a, b); }
+	
+	public void updateBigDecimal(int a, BigDecimal b) throws SQLException
+	{ inner.updateBigDecimal(a, b); }
+	
+	public void updateBigDecimal(String a, BigDecimal b) throws SQLException
+	{ inner.updateBigDecimal(a, b); }
+	
+	public void updateString(String a, String b) throws SQLException
+	{ inner.updateString(a, b); }
+	
+	public void updateString(int a, String b) throws SQLException
+	{ inner.updateString(a, b); }
+	
+	public void updateBytes(int a, byte[] b) throws SQLException
+	{ inner.updateBytes(a, b); }
+	
+	public void updateBytes(String a, byte[] b) throws SQLException
+	{ inner.updateBytes(a, b); }
+	
+	public void updateDate(String a, Date b) throws SQLException
+	{ inner.updateDate(a, b); }
+	
+	public void updateDate(int a, Date b) throws SQLException
+	{ inner.updateDate(a, b); }
+	
+	public void updateTimestamp(int a, Timestamp b) throws SQLException
+	{ inner.updateTimestamp(a, b); }
+	
+	public void updateTimestamp(String a, Timestamp b) throws SQLException
+	{ inner.updateTimestamp(a, b); }
+	
+	public void updateAsciiStream(String a, InputStream b, int c) throws SQLException
+	{ inner.updateAsciiStream(a, b, c); }
+	
+	public void updateAsciiStream(int a, InputStream b, int c) throws SQLException
+	{ inner.updateAsciiStream(a, b, c); }
+	
+	public void updateBinaryStream(int a, InputStream b, int c) throws SQLException
+	{ inner.updateBinaryStream(a, b, c); }
+	
+	public void updateBinaryStream(String a, InputStream b, int c) throws SQLException
+	{ inner.updateBinaryStream(a, b, c); }
+	
+	public void updateCharacterStream(int a, Reader b, int c) throws SQLException
+	{ inner.updateCharacterStream(a, b, c); }
+	
+	public void updateCharacterStream(String a, Reader b, int c) throws SQLException
+	{ inner.updateCharacterStream(a, b, c); }
+	
+	public void updateObject(String a, Object b) throws SQLException
+	{ inner.updateObject(a, b); }
+	
+	public void updateObject(int a, Object b) throws SQLException
+	{ inner.updateObject(a, b); }
+	
+	public void updateObject(int a, Object b, int c) throws SQLException
+	{ inner.updateObject(a, b, c); }
+	
+	public void updateObject(String a, Object b, int c) throws SQLException
+	{ inner.updateObject(a, b, c); }
+	
+	public void insertRow() throws SQLException
+	{ inner.insertRow(); }
+	
+	public void updateRow() throws SQLException
+	{ inner.updateRow(); }
+	
+	public void deleteRow() throws SQLException
+	{ inner.deleteRow(); }
+	
+	public void refreshRow() throws SQLException
+	{ inner.refreshRow(); }
+	
+	public void cancelRowUpdates() throws SQLException
+	{ inner.cancelRowUpdates(); }
+	
+	public void moveToInsertRow() throws SQLException
+	{ inner.moveToInsertRow(); }
+	
+	public void moveToCurrentRow() throws SQLException
+	{ inner.moveToCurrentRow(); }
+	
+	public Statement getStatement() throws SQLException
+	{ return inner.getStatement(); }
+	
+	public Blob getBlob(String a) throws SQLException
+	{ return inner.getBlob(a); }
+	
+	public Blob getBlob(int a) throws SQLException
+	{ return inner.getBlob(a); }
+	
+	public Clob getClob(String a) throws SQLException
+	{ return inner.getClob(a); }
+	
+	public Clob getClob(int a) throws SQLException
+	{ return inner.getClob(a); }
+	
+	public void updateRef(String a, Ref b) throws SQLException
+	{ inner.updateRef(a, b); }
+	
+	public void updateRef(int a, Ref b) throws SQLException
+	{ inner.updateRef(a, b); }
+	
+	public void updateBlob(String a, Blob b) throws SQLException
+	{ inner.updateBlob(a, b); }
+	
+	public void updateBlob(int a, Blob b) throws SQLException
+	{ inner.updateBlob(a, b); }
+	
+	public void updateClob(int a, Clob b) throws SQLException
+	{ inner.updateClob(a, b); }
+	
+	public void updateClob(String a, Clob b) throws SQLException
+	{ inner.updateClob(a, b); }
+	
+	public void updateArray(String a, Array b) throws SQLException
+	{ inner.updateArray(a, b); }
+	
+	public void updateArray(int a, Array b) throws SQLException
+	{ inner.updateArray(a, b); }
+	
+	public Object getObject(int a) throws SQLException
+	{ return inner.getObject(a); }
+	
+	public Object getObject(String a, Map b) throws SQLException
+	{ return inner.getObject(a, b); }
+	
+	public Object getObject(String a) throws SQLException
+	{ return inner.getObject(a); }
+	
+	public Object getObject(int a, Map b) throws SQLException
+	{ return inner.getObject(a, b); }
+	
+	public boolean getBoolean(int a) throws SQLException
+	{ return inner.getBoolean(a); }
+	
+	public boolean getBoolean(String a) throws SQLException
+	{ return inner.getBoolean(a); }
+	
+	public byte getByte(String a) throws SQLException
+	{ return inner.getByte(a); }
+	
+	public byte getByte(int a) throws SQLException
+	{ return inner.getByte(a); }
+	
+	public short getShort(String a) throws SQLException
+	{ return inner.getShort(a); }
+	
+	public short getShort(int a) throws SQLException
+	{ return inner.getShort(a); }
+	
+	public int getInt(String a) throws SQLException
+	{ return inner.getInt(a); }
+	
+	public int getInt(int a) throws SQLException
+	{ return inner.getInt(a); }
+	
+	public long getLong(int a) throws SQLException
+	{ return inner.getLong(a); }
+	
+	public long getLong(String a) throws SQLException
+	{ return inner.getLong(a); }
+	
+	public float getFloat(String a) throws SQLException
+	{ return inner.getFloat(a); }
+	
+	public float getFloat(int a) throws SQLException
+	{ return inner.getFloat(a); }
+	
+	public double getDouble(int a) throws SQLException
+	{ return inner.getDouble(a); }
+	
+	public double getDouble(String a) throws SQLException
+	{ return inner.getDouble(a); }
+	
+	public byte[] getBytes(String a) throws SQLException
+	{ return inner.getBytes(a); }
+	
+	public byte[] getBytes(int a) throws SQLException
+	{ return inner.getBytes(a); }
+	
+	public boolean next() throws SQLException
+	{ return inner.next(); }
+	
+	public URL getURL(int a) throws SQLException
+	{ return inner.getURL(a); }
+	
+	public URL getURL(String a) throws SQLException
+	{ return inner.getURL(a); }
+	
+	public int getType() throws SQLException
+	{ return inner.getType(); }
+	
+	public boolean previous() throws SQLException
+	{ return inner.previous(); }
+	
+	public void close() throws SQLException
+	{ inner.close(); }
+	
+	public String getString(String a) throws SQLException
+	{ return inner.getString(a); }
+	
+	public String getString(int a) throws SQLException
+	{ return inner.getString(a); }
+	
+	public Ref getRef(String a) throws SQLException
+	{ return inner.getRef(a); }
+	
+	public Ref getRef(int a) throws SQLException
+	{ return inner.getRef(a); }
+	
+	public Time getTime(int a, Calendar b) throws SQLException
+	{ return inner.getTime(a, b); }
+	
+	public Time getTime(String a) throws SQLException
+	{ return inner.getTime(a); }
+	
+	public Time getTime(int a) throws SQLException
+	{ return inner.getTime(a); }
+	
+	public Time getTime(String a, Calendar b) throws SQLException
+	{ return inner.getTime(a, b); }
+	
+	public Date getDate(String a) throws SQLException
+	{ return inner.getDate(a); }
+	
+	public Date getDate(int a) throws SQLException
+	{ return inner.getDate(a); }
+	
+	public Date getDate(int a, Calendar b) throws SQLException
+	{ return inner.getDate(a, b); }
+	
+	public Date getDate(String a, Calendar b) throws SQLException
+	{ return inner.getDate(a, b); }
+	
+	public boolean first() throws SQLException
+	{ return inner.first(); }
+	
+	public boolean last() throws SQLException
+	{ return inner.last(); }
+	
+	public Array getArray(String a) throws SQLException
+	{ return inner.getArray(a); }
+	
+	public Array getArray(int a) throws SQLException
+	{ return inner.getArray(a); }
+	
+	public boolean relative(int a) throws SQLException
+	{ return inner.relative(a); }
+	
+	public void updateTime(String a, Time b) throws SQLException
+	{ inner.updateTime(a, b); }
+	
+	public void updateTime(int a, Time b) throws SQLException
+	{ inner.updateTime(a, b); }
+	
+	public int findColumn(String a) throws SQLException
+	{ return inner.findColumn(a); }
+	
+	public int getRow() throws SQLException
+	{ return inner.getRow(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/FilterStatement.java b/src/classes/com/mchange/v2/sql/filter/FilterStatement.java
new file mode 100644
index 0000000..50f3161
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/FilterStatement.java
@@ -0,0 +1,159 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.lang.String;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+
+public abstract class FilterStatement implements Statement
+{
+	protected Statement inner;
+	
+	public FilterStatement(Statement inner)
+	{ this.inner = inner; }
+	
+	public FilterStatement()
+	{}
+	
+	public void setInner( Statement inner )
+	{ this.inner = inner; }
+	
+	public Statement getInner()
+	{ return inner; }
+	
+	public SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public void setFetchDirection(int a) throws SQLException
+	{ inner.setFetchDirection(a); }
+	
+	public int getFetchDirection() throws SQLException
+	{ return inner.getFetchDirection(); }
+	
+	public void setFetchSize(int a) throws SQLException
+	{ inner.setFetchSize(a); }
+	
+	public int getFetchSize() throws SQLException
+	{ return inner.getFetchSize(); }
+	
+	public int getResultSetHoldability() throws SQLException
+	{ return inner.getResultSetHoldability(); }
+	
+	public ResultSet executeQuery(String a) throws SQLException
+	{ return inner.executeQuery(a); }
+	
+	public int executeUpdate(String a, int b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public int executeUpdate(String a, String[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public int executeUpdate(String a, int[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public int executeUpdate(String a) throws SQLException
+	{ return inner.executeUpdate(a); }
+	
+	public int getMaxFieldSize() throws SQLException
+	{ return inner.getMaxFieldSize(); }
+	
+	public void setMaxFieldSize(int a) throws SQLException
+	{ inner.setMaxFieldSize(a); }
+	
+	public int getMaxRows() throws SQLException
+	{ return inner.getMaxRows(); }
+	
+	public void setMaxRows(int a) throws SQLException
+	{ inner.setMaxRows(a); }
+	
+	public void setEscapeProcessing(boolean a) throws SQLException
+	{ inner.setEscapeProcessing(a); }
+	
+	public int getQueryTimeout() throws SQLException
+	{ return inner.getQueryTimeout(); }
+	
+	public void setQueryTimeout(int a) throws SQLException
+	{ inner.setQueryTimeout(a); }
+	
+	public void setCursorName(String a) throws SQLException
+	{ inner.setCursorName(a); }
+	
+	public ResultSet getResultSet() throws SQLException
+	{ return inner.getResultSet(); }
+	
+	public int getUpdateCount() throws SQLException
+	{ return inner.getUpdateCount(); }
+	
+	public boolean getMoreResults() throws SQLException
+	{ return inner.getMoreResults(); }
+	
+	public boolean getMoreResults(int a) throws SQLException
+	{ return inner.getMoreResults(a); }
+	
+	public int getResultSetConcurrency() throws SQLException
+	{ return inner.getResultSetConcurrency(); }
+	
+	public int getResultSetType() throws SQLException
+	{ return inner.getResultSetType(); }
+	
+	public void addBatch(String a) throws SQLException
+	{ inner.addBatch(a); }
+	
+	public void clearBatch() throws SQLException
+	{ inner.clearBatch(); }
+	
+	public int[] executeBatch() throws SQLException
+	{ return inner.executeBatch(); }
+	
+	public ResultSet getGeneratedKeys() throws SQLException
+	{ return inner.getGeneratedKeys(); }
+	
+	public void close() throws SQLException
+	{ inner.close(); }
+	
+	public boolean execute(String a, int b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public boolean execute(String a) throws SQLException
+	{ return inner.execute(a); }
+	
+	public boolean execute(String a, int[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public boolean execute(String a, String[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public Connection getConnection() throws SQLException
+	{ return inner.getConnection(); }
+	
+	public void cancel() throws SQLException
+	{ inner.cancel(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/RecreatePackage.java b/src/classes/com/mchange/v2/sql/filter/RecreatePackage.java
new file mode 100644
index 0000000..95d0286
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/RecreatePackage.java
@@ -0,0 +1,97 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.io.*;
+import java.sql.*;
+import java.lang.reflect.*;
+import com.mchange.v2.codegen.intfc.*;
+import com.mchange.v1.lang.ClassUtils;
+import javax.sql.DataSource;
+
+public final class RecreatePackage
+{
+    final static Class[] intfcs 
+	= new Class[] 
+	{ 
+	    Connection.class, 
+	    ResultSet.class, 
+	    DatabaseMetaData.class, 
+	    Statement.class, 
+	    PreparedStatement.class,
+	    CallableStatement.class,
+	    DataSource.class
+	};
+
+    public static void main( String[] argv )
+    {
+	try
+	    {
+		DelegatorGenerator dg = new DelegatorGenerator();
+		String thisClassName = RecreatePackage.class.getName();
+		String pkg = thisClassName.substring(0, thisClassName.lastIndexOf('.'));
+		for (int i = 0; i < intfcs.length; ++i)
+		    {
+			Class intfcl = intfcs[i];
+			String sin   = ClassUtils.simpleClassName( intfcl );
+			String sgenclass1 = "Filter" + sin;
+			String sgenclass2 = "SynchronizedFilter" + sin;
+			
+			Writer w = null;
+			try
+			    {
+				w = new BufferedWriter( new FileWriter(  sgenclass1 + ".java" ) );
+				dg.setMethodModifiers( Modifier.PUBLIC );
+				dg.writeDelegator( intfcl, pkg + '.' + sgenclass1, w );
+				System.err.println( sgenclass1 );
+			    }
+			finally
+			    {
+				try { if (w != null) w.close(); }
+				catch (Exception e)
+				    { e.printStackTrace(); }
+			    }
+				
+			try
+			    {
+				w = new BufferedWriter( new FileWriter(  sgenclass2 + ".java" ) );
+				dg.setMethodModifiers( Modifier.PUBLIC | Modifier.SYNCHRONIZED );
+				dg.writeDelegator( intfcl, pkg + '.' + sgenclass2, w );
+				System.err.println( sgenclass2 );
+			    }
+			finally
+			    {
+				try { if (w != null) w.close(); }
+				catch (Exception e)
+				    { e.printStackTrace(); }
+			    }
+		    }
+	    }
+	catch ( Exception e )
+	    { e.printStackTrace(); }
+    }
+
+    private RecreatePackage()
+    {}
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterCallableStatement.java b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterCallableStatement.java
new file mode 100644
index 0000000..48db0fb
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterCallableStatement.java
@@ -0,0 +1,523 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.Object;
+import java.lang.String;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.ParameterMetaData;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Map;
+
+public abstract class SynchronizedFilterCallableStatement implements CallableStatement
+{
+	protected CallableStatement inner;
+	
+	public SynchronizedFilterCallableStatement(CallableStatement inner)
+	{ this.inner = inner; }
+	
+	public SynchronizedFilterCallableStatement()
+	{}
+	
+	public synchronized void setInner( CallableStatement inner )
+	{ this.inner = inner; }
+	
+	public synchronized CallableStatement getInner()
+	{ return inner; }
+	
+	public synchronized boolean wasNull() throws SQLException
+	{ return inner.wasNull(); }
+	
+	public synchronized BigDecimal getBigDecimal(int a, int b) throws SQLException
+	{ return inner.getBigDecimal(a, b); }
+	
+	public synchronized BigDecimal getBigDecimal(int a) throws SQLException
+	{ return inner.getBigDecimal(a); }
+	
+	public synchronized BigDecimal getBigDecimal(String a) throws SQLException
+	{ return inner.getBigDecimal(a); }
+	
+	public synchronized Timestamp getTimestamp(String a) throws SQLException
+	{ return inner.getTimestamp(a); }
+	
+	public synchronized Timestamp getTimestamp(String a, Calendar b) throws SQLException
+	{ return inner.getTimestamp(a, b); }
+	
+	public synchronized Timestamp getTimestamp(int a, Calendar b) throws SQLException
+	{ return inner.getTimestamp(a, b); }
+	
+	public synchronized Timestamp getTimestamp(int a) throws SQLException
+	{ return inner.getTimestamp(a); }
+	
+	public synchronized Blob getBlob(String a) throws SQLException
+	{ return inner.getBlob(a); }
+	
+	public synchronized Blob getBlob(int a) throws SQLException
+	{ return inner.getBlob(a); }
+	
+	public synchronized Clob getClob(String a) throws SQLException
+	{ return inner.getClob(a); }
+	
+	public synchronized Clob getClob(int a) throws SQLException
+	{ return inner.getClob(a); }
+	
+	public synchronized void setNull(String a, int b, String c) throws SQLException
+	{ inner.setNull(a, b, c); }
+	
+	public synchronized void setNull(String a, int b) throws SQLException
+	{ inner.setNull(a, b); }
+	
+	public synchronized void setBigDecimal(String a, BigDecimal b) throws SQLException
+	{ inner.setBigDecimal(a, b); }
+	
+	public synchronized void setBytes(String a, byte[] b) throws SQLException
+	{ inner.setBytes(a, b); }
+	
+	public synchronized void setTimestamp(String a, Timestamp b, Calendar c) throws SQLException
+	{ inner.setTimestamp(a, b, c); }
+	
+	public synchronized void setTimestamp(String a, Timestamp b) throws SQLException
+	{ inner.setTimestamp(a, b); }
+	
+	public synchronized void setAsciiStream(String a, InputStream b, int c) throws SQLException
+	{ inner.setAsciiStream(a, b, c); }
+	
+	public synchronized void setBinaryStream(String a, InputStream b, int c) throws SQLException
+	{ inner.setBinaryStream(a, b, c); }
+	
+	public synchronized void setObject(String a, Object b) throws SQLException
+	{ inner.setObject(a, b); }
+	
+	public synchronized void setObject(String a, Object b, int c, int d) throws SQLException
+	{ inner.setObject(a, b, c, d); }
+	
+	public synchronized void setObject(String a, Object b, int c) throws SQLException
+	{ inner.setObject(a, b, c); }
+	
+	public synchronized void setCharacterStream(String a, Reader b, int c) throws SQLException
+	{ inner.setCharacterStream(a, b, c); }
+	
+	public synchronized void registerOutParameter(String a, int b) throws SQLException
+	{ inner.registerOutParameter(a, b); }
+	
+	public synchronized void registerOutParameter(int a, int b) throws SQLException
+	{ inner.registerOutParameter(a, b); }
+	
+	public synchronized void registerOutParameter(int a, int b, int c) throws SQLException
+	{ inner.registerOutParameter(a, b, c); }
+	
+	public synchronized void registerOutParameter(int a, int b, String c) throws SQLException
+	{ inner.registerOutParameter(a, b, c); }
+	
+	public synchronized void registerOutParameter(String a, int b, int c) throws SQLException
+	{ inner.registerOutParameter(a, b, c); }
+	
+	public synchronized void registerOutParameter(String a, int b, String c) throws SQLException
+	{ inner.registerOutParameter(a, b, c); }
+	
+	public synchronized Object getObject(String a, Map b) throws SQLException
+	{ return inner.getObject(a, b); }
+	
+	public synchronized Object getObject(int a, Map b) throws SQLException
+	{ return inner.getObject(a, b); }
+	
+	public synchronized Object getObject(int a) throws SQLException
+	{ return inner.getObject(a); }
+	
+	public synchronized Object getObject(String a) throws SQLException
+	{ return inner.getObject(a); }
+	
+	public synchronized boolean getBoolean(int a) throws SQLException
+	{ return inner.getBoolean(a); }
+	
+	public synchronized boolean getBoolean(String a) throws SQLException
+	{ return inner.getBoolean(a); }
+	
+	public synchronized byte getByte(String a) throws SQLException
+	{ return inner.getByte(a); }
+	
+	public synchronized byte getByte(int a) throws SQLException
+	{ return inner.getByte(a); }
+	
+	public synchronized short getShort(int a) throws SQLException
+	{ return inner.getShort(a); }
+	
+	public synchronized short getShort(String a) throws SQLException
+	{ return inner.getShort(a); }
+	
+	public synchronized int getInt(String a) throws SQLException
+	{ return inner.getInt(a); }
+	
+	public synchronized int getInt(int a) throws SQLException
+	{ return inner.getInt(a); }
+	
+	public synchronized long getLong(int a) throws SQLException
+	{ return inner.getLong(a); }
+	
+	public synchronized long getLong(String a) throws SQLException
+	{ return inner.getLong(a); }
+	
+	public synchronized float getFloat(String a) throws SQLException
+	{ return inner.getFloat(a); }
+	
+	public synchronized float getFloat(int a) throws SQLException
+	{ return inner.getFloat(a); }
+	
+	public synchronized double getDouble(String a) throws SQLException
+	{ return inner.getDouble(a); }
+	
+	public synchronized double getDouble(int a) throws SQLException
+	{ return inner.getDouble(a); }
+	
+	public synchronized byte[] getBytes(int a) throws SQLException
+	{ return inner.getBytes(a); }
+	
+	public synchronized byte[] getBytes(String a) throws SQLException
+	{ return inner.getBytes(a); }
+	
+	public synchronized URL getURL(String a) throws SQLException
+	{ return inner.getURL(a); }
+	
+	public synchronized URL getURL(int a) throws SQLException
+	{ return inner.getURL(a); }
+	
+	public synchronized void setBoolean(String a, boolean b) throws SQLException
+	{ inner.setBoolean(a, b); }
+	
+	public synchronized void setByte(String a, byte b) throws SQLException
+	{ inner.setByte(a, b); }
+	
+	public synchronized void setShort(String a, short b) throws SQLException
+	{ inner.setShort(a, b); }
+	
+	public synchronized void setInt(String a, int b) throws SQLException
+	{ inner.setInt(a, b); }
+	
+	public synchronized void setLong(String a, long b) throws SQLException
+	{ inner.setLong(a, b); }
+	
+	public synchronized void setFloat(String a, float b) throws SQLException
+	{ inner.setFloat(a, b); }
+	
+	public synchronized void setDouble(String a, double b) throws SQLException
+	{ inner.setDouble(a, b); }
+	
+	public synchronized String getString(String a) throws SQLException
+	{ return inner.getString(a); }
+	
+	public synchronized String getString(int a) throws SQLException
+	{ return inner.getString(a); }
+	
+	public synchronized Ref getRef(int a) throws SQLException
+	{ return inner.getRef(a); }
+	
+	public synchronized Ref getRef(String a) throws SQLException
+	{ return inner.getRef(a); }
+	
+	public synchronized void setURL(String a, URL b) throws SQLException
+	{ inner.setURL(a, b); }
+	
+	public synchronized void setTime(String a, Time b) throws SQLException
+	{ inner.setTime(a, b); }
+	
+	public synchronized void setTime(String a, Time b, Calendar c) throws SQLException
+	{ inner.setTime(a, b, c); }
+	
+	public synchronized Time getTime(int a, Calendar b) throws SQLException
+	{ return inner.getTime(a, b); }
+	
+	public synchronized Time getTime(String a) throws SQLException
+	{ return inner.getTime(a); }
+	
+	public synchronized Time getTime(int a) throws SQLException
+	{ return inner.getTime(a); }
+	
+	public synchronized Time getTime(String a, Calendar b) throws SQLException
+	{ return inner.getTime(a, b); }
+	
+	public synchronized Date getDate(int a, Calendar b) throws SQLException
+	{ return inner.getDate(a, b); }
+	
+	public synchronized Date getDate(String a) throws SQLException
+	{ return inner.getDate(a); }
+	
+	public synchronized Date getDate(int a) throws SQLException
+	{ return inner.getDate(a); }
+	
+	public synchronized Date getDate(String a, Calendar b) throws SQLException
+	{ return inner.getDate(a, b); }
+	
+	public synchronized void setString(String a, String b) throws SQLException
+	{ inner.setString(a, b); }
+	
+	public synchronized Array getArray(int a) throws SQLException
+	{ return inner.getArray(a); }
+	
+	public synchronized Array getArray(String a) throws SQLException
+	{ return inner.getArray(a); }
+	
+	public synchronized void setDate(String a, Date b, Calendar c) throws SQLException
+	{ inner.setDate(a, b, c); }
+	
+	public synchronized void setDate(String a, Date b) throws SQLException
+	{ inner.setDate(a, b); }
+	
+	public synchronized ResultSetMetaData getMetaData() throws SQLException
+	{ return inner.getMetaData(); }
+	
+	public synchronized ResultSet executeQuery() throws SQLException
+	{ return inner.executeQuery(); }
+	
+	public synchronized int executeUpdate() throws SQLException
+	{ return inner.executeUpdate(); }
+	
+	public synchronized void addBatch() throws SQLException
+	{ inner.addBatch(); }
+	
+	public synchronized void setNull(int a, int b, String c) throws SQLException
+	{ inner.setNull(a, b, c); }
+	
+	public synchronized void setNull(int a, int b) throws SQLException
+	{ inner.setNull(a, b); }
+	
+	public synchronized void setBigDecimal(int a, BigDecimal b) throws SQLException
+	{ inner.setBigDecimal(a, b); }
+	
+	public synchronized void setBytes(int a, byte[] b) throws SQLException
+	{ inner.setBytes(a, b); }
+	
+	public synchronized void setTimestamp(int a, Timestamp b, Calendar c) throws SQLException
+	{ inner.setTimestamp(a, b, c); }
+	
+	public synchronized void setTimestamp(int a, Timestamp b) throws SQLException
+	{ inner.setTimestamp(a, b); }
+	
+	public synchronized void setAsciiStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setAsciiStream(a, b, c); }
+	
+	public synchronized void setUnicodeStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setUnicodeStream(a, b, c); }
+	
+	public synchronized void setBinaryStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setBinaryStream(a, b, c); }
+	
+	public synchronized void clearParameters() throws SQLException
+	{ inner.clearParameters(); }
+	
+	public synchronized void setObject(int a, Object b) throws SQLException
+	{ inner.setObject(a, b); }
+	
+	public synchronized void setObject(int a, Object b, int c, int d) throws SQLException
+	{ inner.setObject(a, b, c, d); }
+	
+	public synchronized void setObject(int a, Object b, int c) throws SQLException
+	{ inner.setObject(a, b, c); }
+	
+	public synchronized void setCharacterStream(int a, Reader b, int c) throws SQLException
+	{ inner.setCharacterStream(a, b, c); }
+	
+	public synchronized void setRef(int a, Ref b) throws SQLException
+	{ inner.setRef(a, b); }
+	
+	public synchronized void setBlob(int a, Blob b) throws SQLException
+	{ inner.setBlob(a, b); }
+	
+	public synchronized void setClob(int a, Clob b) throws SQLException
+	{ inner.setClob(a, b); }
+	
+	public synchronized void setArray(int a, Array b) throws SQLException
+	{ inner.setArray(a, b); }
+	
+	public synchronized ParameterMetaData getParameterMetaData() throws SQLException
+	{ return inner.getParameterMetaData(); }
+	
+	public synchronized void setBoolean(int a, boolean b) throws SQLException
+	{ inner.setBoolean(a, b); }
+	
+	public synchronized void setByte(int a, byte b) throws SQLException
+	{ inner.setByte(a, b); }
+	
+	public synchronized void setShort(int a, short b) throws SQLException
+	{ inner.setShort(a, b); }
+	
+	public synchronized void setInt(int a, int b) throws SQLException
+	{ inner.setInt(a, b); }
+	
+	public synchronized void setLong(int a, long b) throws SQLException
+	{ inner.setLong(a, b); }
+	
+	public synchronized void setFloat(int a, float b) throws SQLException
+	{ inner.setFloat(a, b); }
+	
+	public synchronized void setDouble(int a, double b) throws SQLException
+	{ inner.setDouble(a, b); }
+	
+	public synchronized void setURL(int a, URL b) throws SQLException
+	{ inner.setURL(a, b); }
+	
+	public synchronized void setTime(int a, Time b) throws SQLException
+	{ inner.setTime(a, b); }
+	
+	public synchronized void setTime(int a, Time b, Calendar c) throws SQLException
+	{ inner.setTime(a, b, c); }
+	
+	public synchronized boolean execute() throws SQLException
+	{ return inner.execute(); }
+	
+	public synchronized void setString(int a, String b) throws SQLException
+	{ inner.setString(a, b); }
+	
+	public synchronized void setDate(int a, Date b, Calendar c) throws SQLException
+	{ inner.setDate(a, b, c); }
+	
+	public synchronized void setDate(int a, Date b) throws SQLException
+	{ inner.setDate(a, b); }
+	
+	public synchronized SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public synchronized void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public synchronized void setFetchDirection(int a) throws SQLException
+	{ inner.setFetchDirection(a); }
+	
+	public synchronized int getFetchDirection() throws SQLException
+	{ return inner.getFetchDirection(); }
+	
+	public synchronized void setFetchSize(int a) throws SQLException
+	{ inner.setFetchSize(a); }
+	
+	public synchronized int getFetchSize() throws SQLException
+	{ return inner.getFetchSize(); }
+	
+	public synchronized int getResultSetHoldability() throws SQLException
+	{ return inner.getResultSetHoldability(); }
+	
+	public synchronized ResultSet executeQuery(String a) throws SQLException
+	{ return inner.executeQuery(a); }
+	
+	public synchronized int executeUpdate(String a, int b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public synchronized int executeUpdate(String a, String[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public synchronized int executeUpdate(String a, int[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public synchronized int executeUpdate(String a) throws SQLException
+	{ return inner.executeUpdate(a); }
+	
+	public synchronized int getMaxFieldSize() throws SQLException
+	{ return inner.getMaxFieldSize(); }
+	
+	public synchronized void setMaxFieldSize(int a) throws SQLException
+	{ inner.setMaxFieldSize(a); }
+	
+	public synchronized int getMaxRows() throws SQLException
+	{ return inner.getMaxRows(); }
+	
+	public synchronized void setMaxRows(int a) throws SQLException
+	{ inner.setMaxRows(a); }
+	
+	public synchronized void setEscapeProcessing(boolean a) throws SQLException
+	{ inner.setEscapeProcessing(a); }
+	
+	public synchronized int getQueryTimeout() throws SQLException
+	{ return inner.getQueryTimeout(); }
+	
+	public synchronized void setQueryTimeout(int a) throws SQLException
+	{ inner.setQueryTimeout(a); }
+	
+	public synchronized void setCursorName(String a) throws SQLException
+	{ inner.setCursorName(a); }
+	
+	public synchronized ResultSet getResultSet() throws SQLException
+	{ return inner.getResultSet(); }
+	
+	public synchronized int getUpdateCount() throws SQLException
+	{ return inner.getUpdateCount(); }
+	
+	public synchronized boolean getMoreResults() throws SQLException
+	{ return inner.getMoreResults(); }
+	
+	public synchronized boolean getMoreResults(int a) throws SQLException
+	{ return inner.getMoreResults(a); }
+	
+	public synchronized int getResultSetConcurrency() throws SQLException
+	{ return inner.getResultSetConcurrency(); }
+	
+	public synchronized int getResultSetType() throws SQLException
+	{ return inner.getResultSetType(); }
+	
+	public synchronized void addBatch(String a) throws SQLException
+	{ inner.addBatch(a); }
+	
+	public synchronized void clearBatch() throws SQLException
+	{ inner.clearBatch(); }
+	
+	public synchronized int[] executeBatch() throws SQLException
+	{ return inner.executeBatch(); }
+	
+	public synchronized ResultSet getGeneratedKeys() throws SQLException
+	{ return inner.getGeneratedKeys(); }
+	
+	public synchronized void close() throws SQLException
+	{ inner.close(); }
+	
+	public synchronized boolean execute(String a, int b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public synchronized boolean execute(String a) throws SQLException
+	{ return inner.execute(a); }
+	
+	public synchronized boolean execute(String a, int[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public synchronized boolean execute(String a, String[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public synchronized Connection getConnection() throws SQLException
+	{ return inner.getConnection(); }
+	
+	public synchronized void cancel() throws SQLException
+	{ inner.cancel(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterConnection.java b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterConnection.java
new file mode 100644
index 0000000..ed1bfd4
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterConnection.java
@@ -0,0 +1,160 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.lang.String;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.util.Map;
+
+public abstract class SynchronizedFilterConnection implements Connection
+{
+	protected Connection inner;
+	
+	public SynchronizedFilterConnection(Connection inner)
+	{ this.inner = inner; }
+	
+	public SynchronizedFilterConnection()
+	{}
+	
+	public synchronized void setInner( Connection inner )
+	{ this.inner = inner; }
+	
+	public synchronized Connection getInner()
+	{ return inner; }
+	
+	public synchronized Statement createStatement(int a, int b, int c) throws SQLException
+	{ return inner.createStatement(a, b, c); }
+	
+	public synchronized Statement createStatement(int a, int b) throws SQLException
+	{ return inner.createStatement(a, b); }
+	
+	public synchronized Statement createStatement() throws SQLException
+	{ return inner.createStatement(); }
+	
+	public synchronized PreparedStatement prepareStatement(String a, String[] b) throws SQLException
+	{ return inner.prepareStatement(a, b); }
+	
+	public synchronized PreparedStatement prepareStatement(String a) throws SQLException
+	{ return inner.prepareStatement(a); }
+	
+	public synchronized PreparedStatement prepareStatement(String a, int b, int c) throws SQLException
+	{ return inner.prepareStatement(a, b, c); }
+	
+	public synchronized PreparedStatement prepareStatement(String a, int b, int c, int d) throws SQLException
+	{ return inner.prepareStatement(a, b, c, d); }
+	
+	public synchronized PreparedStatement prepareStatement(String a, int b) throws SQLException
+	{ return inner.prepareStatement(a, b); }
+	
+	public synchronized PreparedStatement prepareStatement(String a, int[] b) throws SQLException
+	{ return inner.prepareStatement(a, b); }
+	
+	public synchronized CallableStatement prepareCall(String a, int b, int c, int d) throws SQLException
+	{ return inner.prepareCall(a, b, c, d); }
+	
+	public synchronized CallableStatement prepareCall(String a, int b, int c) throws SQLException
+	{ return inner.prepareCall(a, b, c); }
+	
+	public synchronized CallableStatement prepareCall(String a) throws SQLException
+	{ return inner.prepareCall(a); }
+	
+	public synchronized String nativeSQL(String a) throws SQLException
+	{ return inner.nativeSQL(a); }
+	
+	public synchronized void setAutoCommit(boolean a) throws SQLException
+	{ inner.setAutoCommit(a); }
+	
+	public synchronized boolean getAutoCommit() throws SQLException
+	{ return inner.getAutoCommit(); }
+	
+	public synchronized void commit() throws SQLException
+	{ inner.commit(); }
+	
+	public synchronized void rollback(Savepoint a) throws SQLException
+	{ inner.rollback(a); }
+	
+	public synchronized void rollback() throws SQLException
+	{ inner.rollback(); }
+	
+	public synchronized DatabaseMetaData getMetaData() throws SQLException
+	{ return inner.getMetaData(); }
+	
+	public synchronized void setCatalog(String a) throws SQLException
+	{ inner.setCatalog(a); }
+	
+	public synchronized String getCatalog() throws SQLException
+	{ return inner.getCatalog(); }
+	
+	public synchronized void setTransactionIsolation(int a) throws SQLException
+	{ inner.setTransactionIsolation(a); }
+	
+	public synchronized int getTransactionIsolation() throws SQLException
+	{ return inner.getTransactionIsolation(); }
+	
+	public synchronized SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public synchronized void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public synchronized Map getTypeMap() throws SQLException
+	{ return inner.getTypeMap(); }
+	
+	public synchronized void setTypeMap(Map a) throws SQLException
+	{ inner.setTypeMap(a); }
+	
+	public synchronized void setHoldability(int a) throws SQLException
+	{ inner.setHoldability(a); }
+	
+	public synchronized int getHoldability() throws SQLException
+	{ return inner.getHoldability(); }
+	
+	public synchronized Savepoint setSavepoint() throws SQLException
+	{ return inner.setSavepoint(); }
+	
+	public synchronized Savepoint setSavepoint(String a) throws SQLException
+	{ return inner.setSavepoint(a); }
+	
+	public synchronized void releaseSavepoint(Savepoint a) throws SQLException
+	{ inner.releaseSavepoint(a); }
+	
+	public synchronized void setReadOnly(boolean a) throws SQLException
+	{ inner.setReadOnly(a); }
+	
+	public synchronized boolean isReadOnly() throws SQLException
+	{ return inner.isReadOnly(); }
+	
+	public synchronized void close() throws SQLException
+	{ inner.close(); }
+	
+	public synchronized boolean isClosed() throws SQLException
+	{ return inner.isClosed(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterDatabaseMetaData.java b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterDatabaseMetaData.java
new file mode 100644
index 0000000..ac92c80
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterDatabaseMetaData.java
@@ -0,0 +1,542 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.lang.String;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public abstract class SynchronizedFilterDatabaseMetaData implements DatabaseMetaData
+{
+	protected DatabaseMetaData inner;
+	
+	public SynchronizedFilterDatabaseMetaData(DatabaseMetaData inner)
+	{ this.inner = inner; }
+	
+	public SynchronizedFilterDatabaseMetaData()
+	{}
+	
+	public synchronized void setInner( DatabaseMetaData inner )
+	{ this.inner = inner; }
+	
+	public synchronized DatabaseMetaData getInner()
+	{ return inner; }
+	
+	public synchronized boolean allProceduresAreCallable() throws SQLException
+	{ return inner.allProceduresAreCallable(); }
+	
+	public synchronized boolean allTablesAreSelectable() throws SQLException
+	{ return inner.allTablesAreSelectable(); }
+	
+	public synchronized boolean nullsAreSortedHigh() throws SQLException
+	{ return inner.nullsAreSortedHigh(); }
+	
+	public synchronized boolean nullsAreSortedLow() throws SQLException
+	{ return inner.nullsAreSortedLow(); }
+	
+	public synchronized boolean nullsAreSortedAtStart() throws SQLException
+	{ return inner.nullsAreSortedAtStart(); }
+	
+	public synchronized boolean nullsAreSortedAtEnd() throws SQLException
+	{ return inner.nullsAreSortedAtEnd(); }
+	
+	public synchronized String getDatabaseProductName() throws SQLException
+	{ return inner.getDatabaseProductName(); }
+	
+	public synchronized String getDatabaseProductVersion() throws SQLException
+	{ return inner.getDatabaseProductVersion(); }
+	
+	public synchronized String getDriverName() throws SQLException
+	{ return inner.getDriverName(); }
+	
+	public synchronized String getDriverVersion() throws SQLException
+	{ return inner.getDriverVersion(); }
+	
+	public synchronized int getDriverMajorVersion()
+	{ return inner.getDriverMajorVersion(); }
+	
+	public synchronized int getDriverMinorVersion()
+	{ return inner.getDriverMinorVersion(); }
+	
+	public synchronized boolean usesLocalFiles() throws SQLException
+	{ return inner.usesLocalFiles(); }
+	
+	public synchronized boolean usesLocalFilePerTable() throws SQLException
+	{ return inner.usesLocalFilePerTable(); }
+	
+	public synchronized boolean supportsMixedCaseIdentifiers() throws SQLException
+	{ return inner.supportsMixedCaseIdentifiers(); }
+	
+	public synchronized boolean storesUpperCaseIdentifiers() throws SQLException
+	{ return inner.storesUpperCaseIdentifiers(); }
+	
+	public synchronized boolean storesLowerCaseIdentifiers() throws SQLException
+	{ return inner.storesLowerCaseIdentifiers(); }
+	
+	public synchronized boolean storesMixedCaseIdentifiers() throws SQLException
+	{ return inner.storesMixedCaseIdentifiers(); }
+	
+	public synchronized boolean supportsMixedCaseQuotedIdentifiers() throws SQLException
+	{ return inner.supportsMixedCaseQuotedIdentifiers(); }
+	
+	public synchronized boolean storesUpperCaseQuotedIdentifiers() throws SQLException
+	{ return inner.storesUpperCaseQuotedIdentifiers(); }
+	
+	public synchronized boolean storesLowerCaseQuotedIdentifiers() throws SQLException
+	{ return inner.storesLowerCaseQuotedIdentifiers(); }
+	
+	public synchronized boolean storesMixedCaseQuotedIdentifiers() throws SQLException
+	{ return inner.storesMixedCaseQuotedIdentifiers(); }
+	
+	public synchronized String getIdentifierQuoteString() throws SQLException
+	{ return inner.getIdentifierQuoteString(); }
+	
+	public synchronized String getSQLKeywords() throws SQLException
+	{ return inner.getSQLKeywords(); }
+	
+	public synchronized String getNumericFunctions() throws SQLException
+	{ return inner.getNumericFunctions(); }
+	
+	public synchronized String getStringFunctions() throws SQLException
+	{ return inner.getStringFunctions(); }
+	
+	public synchronized String getSystemFunctions() throws SQLException
+	{ return inner.getSystemFunctions(); }
+	
+	public synchronized String getTimeDateFunctions() throws SQLException
+	{ return inner.getTimeDateFunctions(); }
+	
+	public synchronized String getSearchStringEscape() throws SQLException
+	{ return inner.getSearchStringEscape(); }
+	
+	public synchronized String getExtraNameCharacters() throws SQLException
+	{ return inner.getExtraNameCharacters(); }
+	
+	public synchronized boolean supportsAlterTableWithAddColumn() throws SQLException
+	{ return inner.supportsAlterTableWithAddColumn(); }
+	
+	public synchronized boolean supportsAlterTableWithDropColumn() throws SQLException
+	{ return inner.supportsAlterTableWithDropColumn(); }
+	
+	public synchronized boolean supportsColumnAliasing() throws SQLException
+	{ return inner.supportsColumnAliasing(); }
+	
+	public synchronized boolean nullPlusNonNullIsNull() throws SQLException
+	{ return inner.nullPlusNonNullIsNull(); }
+	
+	public synchronized boolean supportsConvert() throws SQLException
+	{ return inner.supportsConvert(); }
+	
+	public synchronized boolean supportsConvert(int a, int b) throws SQLException
+	{ return inner.supportsConvert(a, b); }
+	
+	public synchronized boolean supportsTableCorrelationNames() throws SQLException
+	{ return inner.supportsTableCorrelationNames(); }
+	
+	public synchronized boolean supportsDifferentTableCorrelationNames() throws SQLException
+	{ return inner.supportsDifferentTableCorrelationNames(); }
+	
+	public synchronized boolean supportsExpressionsInOrderBy() throws SQLException
+	{ return inner.supportsExpressionsInOrderBy(); }
+	
+	public synchronized boolean supportsOrderByUnrelated() throws SQLException
+	{ return inner.supportsOrderByUnrelated(); }
+	
+	public synchronized boolean supportsGroupBy() throws SQLException
+	{ return inner.supportsGroupBy(); }
+	
+	public synchronized boolean supportsGroupByUnrelated() throws SQLException
+	{ return inner.supportsGroupByUnrelated(); }
+	
+	public synchronized boolean supportsGroupByBeyondSelect() throws SQLException
+	{ return inner.supportsGroupByBeyondSelect(); }
+	
+	public synchronized boolean supportsLikeEscapeClause() throws SQLException
+	{ return inner.supportsLikeEscapeClause(); }
+	
+	public synchronized boolean supportsMultipleResultSets() throws SQLException
+	{ return inner.supportsMultipleResultSets(); }
+	
+	public synchronized boolean supportsMultipleTransactions() throws SQLException
+	{ return inner.supportsMultipleTransactions(); }
+	
+	public synchronized boolean supportsNonNullableColumns() throws SQLException
+	{ return inner.supportsNonNullableColumns(); }
+	
+	public synchronized boolean supportsMinimumSQLGrammar() throws SQLException
+	{ return inner.supportsMinimumSQLGrammar(); }
+	
+	public synchronized boolean supportsCoreSQLGrammar() throws SQLException
+	{ return inner.supportsCoreSQLGrammar(); }
+	
+	public synchronized boolean supportsExtendedSQLGrammar() throws SQLException
+	{ return inner.supportsExtendedSQLGrammar(); }
+	
+	public synchronized boolean supportsANSI92EntryLevelSQL() throws SQLException
+	{ return inner.supportsANSI92EntryLevelSQL(); }
+	
+	public synchronized boolean supportsANSI92IntermediateSQL() throws SQLException
+	{ return inner.supportsANSI92IntermediateSQL(); }
+	
+	public synchronized boolean supportsANSI92FullSQL() throws SQLException
+	{ return inner.supportsANSI92FullSQL(); }
+	
+	public synchronized boolean supportsIntegrityEnhancementFacility() throws SQLException
+	{ return inner.supportsIntegrityEnhancementFacility(); }
+	
+	public synchronized boolean supportsOuterJoins() throws SQLException
+	{ return inner.supportsOuterJoins(); }
+	
+	public synchronized boolean supportsFullOuterJoins() throws SQLException
+	{ return inner.supportsFullOuterJoins(); }
+	
+	public synchronized boolean supportsLimitedOuterJoins() throws SQLException
+	{ return inner.supportsLimitedOuterJoins(); }
+	
+	public synchronized String getSchemaTerm() throws SQLException
+	{ return inner.getSchemaTerm(); }
+	
+	public synchronized String getProcedureTerm() throws SQLException
+	{ return inner.getProcedureTerm(); }
+	
+	public synchronized String getCatalogTerm() throws SQLException
+	{ return inner.getCatalogTerm(); }
+	
+	public synchronized boolean isCatalogAtStart() throws SQLException
+	{ return inner.isCatalogAtStart(); }
+	
+	public synchronized String getCatalogSeparator() throws SQLException
+	{ return inner.getCatalogSeparator(); }
+	
+	public synchronized boolean supportsSchemasInDataManipulation() throws SQLException
+	{ return inner.supportsSchemasInDataManipulation(); }
+	
+	public synchronized boolean supportsSchemasInProcedureCalls() throws SQLException
+	{ return inner.supportsSchemasInProcedureCalls(); }
+	
+	public synchronized boolean supportsSchemasInTableDefinitions() throws SQLException
+	{ return inner.supportsSchemasInTableDefinitions(); }
+	
+	public synchronized boolean supportsSchemasInIndexDefinitions() throws SQLException
+	{ return inner.supportsSchemasInIndexDefinitions(); }
+	
+	public synchronized boolean supportsSchemasInPrivilegeDefinitions() throws SQLException
+	{ return inner.supportsSchemasInPrivilegeDefinitions(); }
+	
+	public synchronized boolean supportsCatalogsInDataManipulation() throws SQLException
+	{ return inner.supportsCatalogsInDataManipulation(); }
+	
+	public synchronized boolean supportsCatalogsInProcedureCalls() throws SQLException
+	{ return inner.supportsCatalogsInProcedureCalls(); }
+	
+	public synchronized boolean supportsCatalogsInTableDefinitions() throws SQLException
+	{ return inner.supportsCatalogsInTableDefinitions(); }
+	
+	public synchronized boolean supportsCatalogsInIndexDefinitions() throws SQLException
+	{ return inner.supportsCatalogsInIndexDefinitions(); }
+	
+	public synchronized boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException
+	{ return inner.supportsCatalogsInPrivilegeDefinitions(); }
+	
+	public synchronized boolean supportsPositionedDelete() throws SQLException
+	{ return inner.supportsPositionedDelete(); }
+	
+	public synchronized boolean supportsPositionedUpdate() throws SQLException
+	{ return inner.supportsPositionedUpdate(); }
+	
+	public synchronized boolean supportsSelectForUpdate() throws SQLException
+	{ return inner.supportsSelectForUpdate(); }
+	
+	public synchronized boolean supportsStoredProcedures() throws SQLException
+	{ return inner.supportsStoredProcedures(); }
+	
+	public synchronized boolean supportsSubqueriesInComparisons() throws SQLException
+	{ return inner.supportsSubqueriesInComparisons(); }
+	
+	public synchronized boolean supportsSubqueriesInExists() throws SQLException
+	{ return inner.supportsSubqueriesInExists(); }
+	
+	public synchronized boolean supportsSubqueriesInIns() throws SQLException
+	{ return inner.supportsSubqueriesInIns(); }
+	
+	public synchronized boolean supportsSubqueriesInQuantifieds() throws SQLException
+	{ return inner.supportsSubqueriesInQuantifieds(); }
+	
+	public synchronized boolean supportsCorrelatedSubqueries() throws SQLException
+	{ return inner.supportsCorrelatedSubqueries(); }
+	
+	public synchronized boolean supportsUnion() throws SQLException
+	{ return inner.supportsUnion(); }
+	
+	public synchronized boolean supportsUnionAll() throws SQLException
+	{ return inner.supportsUnionAll(); }
+	
+	public synchronized boolean supportsOpenCursorsAcrossCommit() throws SQLException
+	{ return inner.supportsOpenCursorsAcrossCommit(); }
+	
+	public synchronized boolean supportsOpenCursorsAcrossRollback() throws SQLException
+	{ return inner.supportsOpenCursorsAcrossRollback(); }
+	
+	public synchronized boolean supportsOpenStatementsAcrossCommit() throws SQLException
+	{ return inner.supportsOpenStatementsAcrossCommit(); }
+	
+	public synchronized boolean supportsOpenStatementsAcrossRollback() throws SQLException
+	{ return inner.supportsOpenStatementsAcrossRollback(); }
+	
+	public synchronized int getMaxBinaryLiteralLength() throws SQLException
+	{ return inner.getMaxBinaryLiteralLength(); }
+	
+	public synchronized int getMaxCharLiteralLength() throws SQLException
+	{ return inner.getMaxCharLiteralLength(); }
+	
+	public synchronized int getMaxColumnNameLength() throws SQLException
+	{ return inner.getMaxColumnNameLength(); }
+	
+	public synchronized int getMaxColumnsInGroupBy() throws SQLException
+	{ return inner.getMaxColumnsInGroupBy(); }
+	
+	public synchronized int getMaxColumnsInIndex() throws SQLException
+	{ return inner.getMaxColumnsInIndex(); }
+	
+	public synchronized int getMaxColumnsInOrderBy() throws SQLException
+	{ return inner.getMaxColumnsInOrderBy(); }
+	
+	public synchronized int getMaxColumnsInSelect() throws SQLException
+	{ return inner.getMaxColumnsInSelect(); }
+	
+	public synchronized int getMaxColumnsInTable() throws SQLException
+	{ return inner.getMaxColumnsInTable(); }
+	
+	public synchronized int getMaxConnections() throws SQLException
+	{ return inner.getMaxConnections(); }
+	
+	public synchronized int getMaxCursorNameLength() throws SQLException
+	{ return inner.getMaxCursorNameLength(); }
+	
+	public synchronized int getMaxIndexLength() throws SQLException
+	{ return inner.getMaxIndexLength(); }
+	
+	public synchronized int getMaxSchemaNameLength() throws SQLException
+	{ return inner.getMaxSchemaNameLength(); }
+	
+	public synchronized int getMaxProcedureNameLength() throws SQLException
+	{ return inner.getMaxProcedureNameLength(); }
+	
+	public synchronized int getMaxCatalogNameLength() throws SQLException
+	{ return inner.getMaxCatalogNameLength(); }
+	
+	public synchronized int getMaxRowSize() throws SQLException
+	{ return inner.getMaxRowSize(); }
+	
+	public synchronized boolean doesMaxRowSizeIncludeBlobs() throws SQLException
+	{ return inner.doesMaxRowSizeIncludeBlobs(); }
+	
+	public synchronized int getMaxStatementLength() throws SQLException
+	{ return inner.getMaxStatementLength(); }
+	
+	public synchronized int getMaxStatements() throws SQLException
+	{ return inner.getMaxStatements(); }
+	
+	public synchronized int getMaxTableNameLength() throws SQLException
+	{ return inner.getMaxTableNameLength(); }
+	
+	public synchronized int getMaxTablesInSelect() throws SQLException
+	{ return inner.getMaxTablesInSelect(); }
+	
+	public synchronized int getMaxUserNameLength() throws SQLException
+	{ return inner.getMaxUserNameLength(); }
+	
+	public synchronized int getDefaultTransactionIsolation() throws SQLException
+	{ return inner.getDefaultTransactionIsolation(); }
+	
+	public synchronized boolean supportsTransactions() throws SQLException
+	{ return inner.supportsTransactions(); }
+	
+	public synchronized boolean supportsTransactionIsolationLevel(int a) throws SQLException
+	{ return inner.supportsTransactionIsolationLevel(a); }
+	
+	public synchronized boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException
+	{ return inner.supportsDataDefinitionAndDataManipulationTransactions(); }
+	
+	public synchronized boolean supportsDataManipulationTransactionsOnly() throws SQLException
+	{ return inner.supportsDataManipulationTransactionsOnly(); }
+	
+	public synchronized boolean dataDefinitionCausesTransactionCommit() throws SQLException
+	{ return inner.dataDefinitionCausesTransactionCommit(); }
+	
+	public synchronized boolean dataDefinitionIgnoredInTransactions() throws SQLException
+	{ return inner.dataDefinitionIgnoredInTransactions(); }
+	
+	public synchronized ResultSet getProcedures(String a, String b, String c) throws SQLException
+	{ return inner.getProcedures(a, b, c); }
+	
+	public synchronized ResultSet getProcedureColumns(String a, String b, String c, String d) throws SQLException
+	{ return inner.getProcedureColumns(a, b, c, d); }
+	
+	public synchronized ResultSet getTables(String a, String b, String c, String[] d) throws SQLException
+	{ return inner.getTables(a, b, c, d); }
+	
+	public synchronized ResultSet getSchemas() throws SQLException
+	{ return inner.getSchemas(); }
+	
+	public synchronized ResultSet getCatalogs() throws SQLException
+	{ return inner.getCatalogs(); }
+	
+	public synchronized ResultSet getTableTypes() throws SQLException
+	{ return inner.getTableTypes(); }
+	
+	public synchronized ResultSet getColumnPrivileges(String a, String b, String c, String d) throws SQLException
+	{ return inner.getColumnPrivileges(a, b, c, d); }
+	
+	public synchronized ResultSet getTablePrivileges(String a, String b, String c) throws SQLException
+	{ return inner.getTablePrivileges(a, b, c); }
+	
+	public synchronized ResultSet getBestRowIdentifier(String a, String b, String c, int d, boolean e) throws SQLException
+	{ return inner.getBestRowIdentifier(a, b, c, d, e); }
+	
+	public synchronized ResultSet getVersionColumns(String a, String b, String c) throws SQLException
+	{ return inner.getVersionColumns(a, b, c); }
+	
+	public synchronized ResultSet getPrimaryKeys(String a, String b, String c) throws SQLException
+	{ return inner.getPrimaryKeys(a, b, c); }
+	
+	public synchronized ResultSet getImportedKeys(String a, String b, String c) throws SQLException
+	{ return inner.getImportedKeys(a, b, c); }
+	
+	public synchronized ResultSet getExportedKeys(String a, String b, String c) throws SQLException
+	{ return inner.getExportedKeys(a, b, c); }
+	
+	public synchronized ResultSet getCrossReference(String a, String b, String c, String d, String e, String f) throws SQLException
+	{ return inner.getCrossReference(a, b, c, d, e, f); }
+	
+	public synchronized ResultSet getTypeInfo() throws SQLException
+	{ return inner.getTypeInfo(); }
+	
+	public synchronized ResultSet getIndexInfo(String a, String b, String c, boolean d, boolean e) throws SQLException
+	{ return inner.getIndexInfo(a, b, c, d, e); }
+	
+	public synchronized boolean supportsResultSetType(int a) throws SQLException
+	{ return inner.supportsResultSetType(a); }
+	
+	public synchronized boolean supportsResultSetConcurrency(int a, int b) throws SQLException
+	{ return inner.supportsResultSetConcurrency(a, b); }
+	
+	public synchronized boolean ownUpdatesAreVisible(int a) throws SQLException
+	{ return inner.ownUpdatesAreVisible(a); }
+	
+	public synchronized boolean ownDeletesAreVisible(int a) throws SQLException
+	{ return inner.ownDeletesAreVisible(a); }
+	
+	public synchronized boolean ownInsertsAreVisible(int a) throws SQLException
+	{ return inner.ownInsertsAreVisible(a); }
+	
+	public synchronized boolean othersUpdatesAreVisible(int a) throws SQLException
+	{ return inner.othersUpdatesAreVisible(a); }
+	
+	public synchronized boolean othersDeletesAreVisible(int a) throws SQLException
+	{ return inner.othersDeletesAreVisible(a); }
+	
+	public synchronized boolean othersInsertsAreVisible(int a) throws SQLException
+	{ return inner.othersInsertsAreVisible(a); }
+	
+	public synchronized boolean updatesAreDetected(int a) throws SQLException
+	{ return inner.updatesAreDetected(a); }
+	
+	public synchronized boolean deletesAreDetected(int a) throws SQLException
+	{ return inner.deletesAreDetected(a); }
+	
+	public synchronized boolean insertsAreDetected(int a) throws SQLException
+	{ return inner.insertsAreDetected(a); }
+	
+	public synchronized boolean supportsBatchUpdates() throws SQLException
+	{ return inner.supportsBatchUpdates(); }
+	
+	public synchronized ResultSet getUDTs(String a, String b, String c, int[] d) throws SQLException
+	{ return inner.getUDTs(a, b, c, d); }
+	
+	public synchronized boolean supportsSavepoints() throws SQLException
+	{ return inner.supportsSavepoints(); }
+	
+	public synchronized boolean supportsNamedParameters() throws SQLException
+	{ return inner.supportsNamedParameters(); }
+	
+	public synchronized boolean supportsMultipleOpenResults() throws SQLException
+	{ return inner.supportsMultipleOpenResults(); }
+	
+	public synchronized boolean supportsGetGeneratedKeys() throws SQLException
+	{ return inner.supportsGetGeneratedKeys(); }
+	
+	public synchronized ResultSet getSuperTypes(String a, String b, String c) throws SQLException
+	{ return inner.getSuperTypes(a, b, c); }
+	
+	public synchronized ResultSet getSuperTables(String a, String b, String c) throws SQLException
+	{ return inner.getSuperTables(a, b, c); }
+	
+	public synchronized boolean supportsResultSetHoldability(int a) throws SQLException
+	{ return inner.supportsResultSetHoldability(a); }
+	
+	public synchronized int getResultSetHoldability() throws SQLException
+	{ return inner.getResultSetHoldability(); }
+	
+	public synchronized int getDatabaseMajorVersion() throws SQLException
+	{ return inner.getDatabaseMajorVersion(); }
+	
+	public synchronized int getDatabaseMinorVersion() throws SQLException
+	{ return inner.getDatabaseMinorVersion(); }
+	
+	public synchronized int getJDBCMajorVersion() throws SQLException
+	{ return inner.getJDBCMajorVersion(); }
+	
+	public synchronized int getJDBCMinorVersion() throws SQLException
+	{ return inner.getJDBCMinorVersion(); }
+	
+	public synchronized int getSQLStateType() throws SQLException
+	{ return inner.getSQLStateType(); }
+	
+	public synchronized boolean locatorsUpdateCopy() throws SQLException
+	{ return inner.locatorsUpdateCopy(); }
+	
+	public synchronized boolean supportsStatementPooling() throws SQLException
+	{ return inner.supportsStatementPooling(); }
+	
+	public synchronized String getURL() throws SQLException
+	{ return inner.getURL(); }
+	
+	public synchronized boolean isReadOnly() throws SQLException
+	{ return inner.isReadOnly(); }
+	
+	public synchronized ResultSet getAttributes(String a, String b, String c, String d) throws SQLException
+	{ return inner.getAttributes(a, b, c, d); }
+	
+	public synchronized Connection getConnection() throws SQLException
+	{ return inner.getConnection(); }
+	
+	public synchronized ResultSet getColumns(String a, String b, String c, String d) throws SQLException
+	{ return inner.getColumns(a, b, c, d); }
+	
+	public synchronized String getUserName() throws SQLException
+	{ return inner.getUserName(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterPreparedStatement.java b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterPreparedStatement.java
new file mode 100644
index 0000000..65062f2
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterPreparedStatement.java
@@ -0,0 +1,285 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.Object;
+import java.lang.String;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+
+public abstract class SynchronizedFilterPreparedStatement implements PreparedStatement
+{
+	protected PreparedStatement inner;
+	
+	public SynchronizedFilterPreparedStatement(PreparedStatement inner)
+	{ this.inner = inner; }
+	
+	public SynchronizedFilterPreparedStatement()
+	{}
+	
+	public synchronized void setInner( PreparedStatement inner )
+	{ this.inner = inner; }
+	
+	public synchronized PreparedStatement getInner()
+	{ return inner; }
+	
+	public synchronized ResultSetMetaData getMetaData() throws SQLException
+	{ return inner.getMetaData(); }
+	
+	public synchronized ResultSet executeQuery() throws SQLException
+	{ return inner.executeQuery(); }
+	
+	public synchronized int executeUpdate() throws SQLException
+	{ return inner.executeUpdate(); }
+	
+	public synchronized void addBatch() throws SQLException
+	{ inner.addBatch(); }
+	
+	public synchronized void setNull(int a, int b, String c) throws SQLException
+	{ inner.setNull(a, b, c); }
+	
+	public synchronized void setNull(int a, int b) throws SQLException
+	{ inner.setNull(a, b); }
+	
+	public synchronized void setBigDecimal(int a, BigDecimal b) throws SQLException
+	{ inner.setBigDecimal(a, b); }
+	
+	public synchronized void setBytes(int a, byte[] b) throws SQLException
+	{ inner.setBytes(a, b); }
+	
+	public synchronized void setTimestamp(int a, Timestamp b, Calendar c) throws SQLException
+	{ inner.setTimestamp(a, b, c); }
+	
+	public synchronized void setTimestamp(int a, Timestamp b) throws SQLException
+	{ inner.setTimestamp(a, b); }
+	
+	public synchronized void setAsciiStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setAsciiStream(a, b, c); }
+	
+	public synchronized void setUnicodeStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setUnicodeStream(a, b, c); }
+	
+	public synchronized void setBinaryStream(int a, InputStream b, int c) throws SQLException
+	{ inner.setBinaryStream(a, b, c); }
+	
+	public synchronized void clearParameters() throws SQLException
+	{ inner.clearParameters(); }
+	
+	public synchronized void setObject(int a, Object b) throws SQLException
+	{ inner.setObject(a, b); }
+	
+	public synchronized void setObject(int a, Object b, int c, int d) throws SQLException
+	{ inner.setObject(a, b, c, d); }
+	
+	public synchronized void setObject(int a, Object b, int c) throws SQLException
+	{ inner.setObject(a, b, c); }
+	
+	public synchronized void setCharacterStream(int a, Reader b, int c) throws SQLException
+	{ inner.setCharacterStream(a, b, c); }
+	
+	public synchronized void setRef(int a, Ref b) throws SQLException
+	{ inner.setRef(a, b); }
+	
+	public synchronized void setBlob(int a, Blob b) throws SQLException
+	{ inner.setBlob(a, b); }
+	
+	public synchronized void setClob(int a, Clob b) throws SQLException
+	{ inner.setClob(a, b); }
+	
+	public synchronized void setArray(int a, Array b) throws SQLException
+	{ inner.setArray(a, b); }
+	
+	public synchronized ParameterMetaData getParameterMetaData() throws SQLException
+	{ return inner.getParameterMetaData(); }
+	
+	public synchronized void setBoolean(int a, boolean b) throws SQLException
+	{ inner.setBoolean(a, b); }
+	
+	public synchronized void setByte(int a, byte b) throws SQLException
+	{ inner.setByte(a, b); }
+	
+	public synchronized void setShort(int a, short b) throws SQLException
+	{ inner.setShort(a, b); }
+	
+	public synchronized void setInt(int a, int b) throws SQLException
+	{ inner.setInt(a, b); }
+	
+	public synchronized void setLong(int a, long b) throws SQLException
+	{ inner.setLong(a, b); }
+	
+	public synchronized void setFloat(int a, float b) throws SQLException
+	{ inner.setFloat(a, b); }
+	
+	public synchronized void setDouble(int a, double b) throws SQLException
+	{ inner.setDouble(a, b); }
+	
+	public synchronized void setURL(int a, URL b) throws SQLException
+	{ inner.setURL(a, b); }
+	
+	public synchronized void setTime(int a, Time b) throws SQLException
+	{ inner.setTime(a, b); }
+	
+	public synchronized void setTime(int a, Time b, Calendar c) throws SQLException
+	{ inner.setTime(a, b, c); }
+	
+	public synchronized boolean execute() throws SQLException
+	{ return inner.execute(); }
+	
+	public synchronized void setString(int a, String b) throws SQLException
+	{ inner.setString(a, b); }
+	
+	public synchronized void setDate(int a, Date b, Calendar c) throws SQLException
+	{ inner.setDate(a, b, c); }
+	
+	public synchronized void setDate(int a, Date b) throws SQLException
+	{ inner.setDate(a, b); }
+	
+	public synchronized SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public synchronized void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public synchronized void setFetchDirection(int a) throws SQLException
+	{ inner.setFetchDirection(a); }
+	
+	public synchronized int getFetchDirection() throws SQLException
+	{ return inner.getFetchDirection(); }
+	
+	public synchronized void setFetchSize(int a) throws SQLException
+	{ inner.setFetchSize(a); }
+	
+	public synchronized int getFetchSize() throws SQLException
+	{ return inner.getFetchSize(); }
+	
+	public synchronized int getResultSetHoldability() throws SQLException
+	{ return inner.getResultSetHoldability(); }
+	
+	public synchronized ResultSet executeQuery(String a) throws SQLException
+	{ return inner.executeQuery(a); }
+	
+	public synchronized int executeUpdate(String a, int b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public synchronized int executeUpdate(String a, String[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public synchronized int executeUpdate(String a, int[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public synchronized int executeUpdate(String a) throws SQLException
+	{ return inner.executeUpdate(a); }
+	
+	public synchronized int getMaxFieldSize() throws SQLException
+	{ return inner.getMaxFieldSize(); }
+	
+	public synchronized void setMaxFieldSize(int a) throws SQLException
+	{ inner.setMaxFieldSize(a); }
+	
+	public synchronized int getMaxRows() throws SQLException
+	{ return inner.getMaxRows(); }
+	
+	public synchronized void setMaxRows(int a) throws SQLException
+	{ inner.setMaxRows(a); }
+	
+	public synchronized void setEscapeProcessing(boolean a) throws SQLException
+	{ inner.setEscapeProcessing(a); }
+	
+	public synchronized int getQueryTimeout() throws SQLException
+	{ return inner.getQueryTimeout(); }
+	
+	public synchronized void setQueryTimeout(int a) throws SQLException
+	{ inner.setQueryTimeout(a); }
+	
+	public synchronized void setCursorName(String a) throws SQLException
+	{ inner.setCursorName(a); }
+	
+	public synchronized ResultSet getResultSet() throws SQLException
+	{ return inner.getResultSet(); }
+	
+	public synchronized int getUpdateCount() throws SQLException
+	{ return inner.getUpdateCount(); }
+	
+	public synchronized boolean getMoreResults() throws SQLException
+	{ return inner.getMoreResults(); }
+	
+	public synchronized boolean getMoreResults(int a) throws SQLException
+	{ return inner.getMoreResults(a); }
+	
+	public synchronized int getResultSetConcurrency() throws SQLException
+	{ return inner.getResultSetConcurrency(); }
+	
+	public synchronized int getResultSetType() throws SQLException
+	{ return inner.getResultSetType(); }
+	
+	public synchronized void addBatch(String a) throws SQLException
+	{ inner.addBatch(a); }
+	
+	public synchronized void clearBatch() throws SQLException
+	{ inner.clearBatch(); }
+	
+	public synchronized int[] executeBatch() throws SQLException
+	{ return inner.executeBatch(); }
+	
+	public synchronized ResultSet getGeneratedKeys() throws SQLException
+	{ return inner.getGeneratedKeys(); }
+	
+	public synchronized void close() throws SQLException
+	{ inner.close(); }
+	
+	public synchronized boolean execute(String a, int b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public synchronized boolean execute(String a) throws SQLException
+	{ return inner.execute(a); }
+	
+	public synchronized boolean execute(String a, int[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public synchronized boolean execute(String a, String[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public synchronized Connection getConnection() throws SQLException
+	{ return inner.getConnection(); }
+	
+	public synchronized void cancel() throws SQLException
+	{ inner.cancel(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterResultSet.java b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterResultSet.java
new file mode 100644
index 0000000..6dbbc27
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterResultSet.java
@@ -0,0 +1,479 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.Object;
+import java.lang.String;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Map;
+
+public abstract class SynchronizedFilterResultSet implements ResultSet
+{
+	protected ResultSet inner;
+	
+	public SynchronizedFilterResultSet(ResultSet inner)
+	{ this.inner = inner; }
+	
+	public SynchronizedFilterResultSet()
+	{}
+	
+	public synchronized void setInner( ResultSet inner )
+	{ this.inner = inner; }
+	
+	public synchronized ResultSet getInner()
+	{ return inner; }
+	
+	public synchronized ResultSetMetaData getMetaData() throws SQLException
+	{ return inner.getMetaData(); }
+	
+	public synchronized SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public synchronized void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public synchronized boolean wasNull() throws SQLException
+	{ return inner.wasNull(); }
+	
+	public synchronized BigDecimal getBigDecimal(int a) throws SQLException
+	{ return inner.getBigDecimal(a); }
+	
+	public synchronized BigDecimal getBigDecimal(String a, int b) throws SQLException
+	{ return inner.getBigDecimal(a, b); }
+	
+	public synchronized BigDecimal getBigDecimal(int a, int b) throws SQLException
+	{ return inner.getBigDecimal(a, b); }
+	
+	public synchronized BigDecimal getBigDecimal(String a) throws SQLException
+	{ return inner.getBigDecimal(a); }
+	
+	public synchronized Timestamp getTimestamp(int a) throws SQLException
+	{ return inner.getTimestamp(a); }
+	
+	public synchronized Timestamp getTimestamp(String a) throws SQLException
+	{ return inner.getTimestamp(a); }
+	
+	public synchronized Timestamp getTimestamp(int a, Calendar b) throws SQLException
+	{ return inner.getTimestamp(a, b); }
+	
+	public synchronized Timestamp getTimestamp(String a, Calendar b) throws SQLException
+	{ return inner.getTimestamp(a, b); }
+	
+	public synchronized InputStream getAsciiStream(String a) throws SQLException
+	{ return inner.getAsciiStream(a); }
+	
+	public synchronized InputStream getAsciiStream(int a) throws SQLException
+	{ return inner.getAsciiStream(a); }
+	
+	public synchronized InputStream getUnicodeStream(String a) throws SQLException
+	{ return inner.getUnicodeStream(a); }
+	
+	public synchronized InputStream getUnicodeStream(int a) throws SQLException
+	{ return inner.getUnicodeStream(a); }
+	
+	public synchronized InputStream getBinaryStream(int a) throws SQLException
+	{ return inner.getBinaryStream(a); }
+	
+	public synchronized InputStream getBinaryStream(String a) throws SQLException
+	{ return inner.getBinaryStream(a); }
+	
+	public synchronized String getCursorName() throws SQLException
+	{ return inner.getCursorName(); }
+	
+	public synchronized Reader getCharacterStream(int a) throws SQLException
+	{ return inner.getCharacterStream(a); }
+	
+	public synchronized Reader getCharacterStream(String a) throws SQLException
+	{ return inner.getCharacterStream(a); }
+	
+	public synchronized boolean isBeforeFirst() throws SQLException
+	{ return inner.isBeforeFirst(); }
+	
+	public synchronized boolean isAfterLast() throws SQLException
+	{ return inner.isAfterLast(); }
+	
+	public synchronized boolean isFirst() throws SQLException
+	{ return inner.isFirst(); }
+	
+	public synchronized boolean isLast() throws SQLException
+	{ return inner.isLast(); }
+	
+	public synchronized void beforeFirst() throws SQLException
+	{ inner.beforeFirst(); }
+	
+	public synchronized void afterLast() throws SQLException
+	{ inner.afterLast(); }
+	
+	public synchronized boolean absolute(int a) throws SQLException
+	{ return inner.absolute(a); }
+	
+	public synchronized void setFetchDirection(int a) throws SQLException
+	{ inner.setFetchDirection(a); }
+	
+	public synchronized int getFetchDirection() throws SQLException
+	{ return inner.getFetchDirection(); }
+	
+	public synchronized void setFetchSize(int a) throws SQLException
+	{ inner.setFetchSize(a); }
+	
+	public synchronized int getFetchSize() throws SQLException
+	{ return inner.getFetchSize(); }
+	
+	public synchronized int getConcurrency() throws SQLException
+	{ return inner.getConcurrency(); }
+	
+	public synchronized boolean rowUpdated() throws SQLException
+	{ return inner.rowUpdated(); }
+	
+	public synchronized boolean rowInserted() throws SQLException
+	{ return inner.rowInserted(); }
+	
+	public synchronized boolean rowDeleted() throws SQLException
+	{ return inner.rowDeleted(); }
+	
+	public synchronized void updateNull(int a) throws SQLException
+	{ inner.updateNull(a); }
+	
+	public synchronized void updateNull(String a) throws SQLException
+	{ inner.updateNull(a); }
+	
+	public synchronized void updateBoolean(int a, boolean b) throws SQLException
+	{ inner.updateBoolean(a, b); }
+	
+	public synchronized void updateBoolean(String a, boolean b) throws SQLException
+	{ inner.updateBoolean(a, b); }
+	
+	public synchronized void updateByte(int a, byte b) throws SQLException
+	{ inner.updateByte(a, b); }
+	
+	public synchronized void updateByte(String a, byte b) throws SQLException
+	{ inner.updateByte(a, b); }
+	
+	public synchronized void updateShort(int a, short b) throws SQLException
+	{ inner.updateShort(a, b); }
+	
+	public synchronized void updateShort(String a, short b) throws SQLException
+	{ inner.updateShort(a, b); }
+	
+	public synchronized void updateInt(String a, int b) throws SQLException
+	{ inner.updateInt(a, b); }
+	
+	public synchronized void updateInt(int a, int b) throws SQLException
+	{ inner.updateInt(a, b); }
+	
+	public synchronized void updateLong(int a, long b) throws SQLException
+	{ inner.updateLong(a, b); }
+	
+	public synchronized void updateLong(String a, long b) throws SQLException
+	{ inner.updateLong(a, b); }
+	
+	public synchronized void updateFloat(String a, float b) throws SQLException
+	{ inner.updateFloat(a, b); }
+	
+	public synchronized void updateFloat(int a, float b) throws SQLException
+	{ inner.updateFloat(a, b); }
+	
+	public synchronized void updateDouble(String a, double b) throws SQLException
+	{ inner.updateDouble(a, b); }
+	
+	public synchronized void updateDouble(int a, double b) throws SQLException
+	{ inner.updateDouble(a, b); }
+	
+	public synchronized void updateBigDecimal(int a, BigDecimal b) throws SQLException
+	{ inner.updateBigDecimal(a, b); }
+	
+	public synchronized void updateBigDecimal(String a, BigDecimal b) throws SQLException
+	{ inner.updateBigDecimal(a, b); }
+	
+	public synchronized void updateString(String a, String b) throws SQLException
+	{ inner.updateString(a, b); }
+	
+	public synchronized void updateString(int a, String b) throws SQLException
+	{ inner.updateString(a, b); }
+	
+	public synchronized void updateBytes(int a, byte[] b) throws SQLException
+	{ inner.updateBytes(a, b); }
+	
+	public synchronized void updateBytes(String a, byte[] b) throws SQLException
+	{ inner.updateBytes(a, b); }
+	
+	public synchronized void updateDate(String a, Date b) throws SQLException
+	{ inner.updateDate(a, b); }
+	
+	public synchronized void updateDate(int a, Date b) throws SQLException
+	{ inner.updateDate(a, b); }
+	
+	public synchronized void updateTimestamp(int a, Timestamp b) throws SQLException
+	{ inner.updateTimestamp(a, b); }
+	
+	public synchronized void updateTimestamp(String a, Timestamp b) throws SQLException
+	{ inner.updateTimestamp(a, b); }
+	
+	public synchronized void updateAsciiStream(String a, InputStream b, int c) throws SQLException
+	{ inner.updateAsciiStream(a, b, c); }
+	
+	public synchronized void updateAsciiStream(int a, InputStream b, int c) throws SQLException
+	{ inner.updateAsciiStream(a, b, c); }
+	
+	public synchronized void updateBinaryStream(int a, InputStream b, int c) throws SQLException
+	{ inner.updateBinaryStream(a, b, c); }
+	
+	public synchronized void updateBinaryStream(String a, InputStream b, int c) throws SQLException
+	{ inner.updateBinaryStream(a, b, c); }
+	
+	public synchronized void updateCharacterStream(int a, Reader b, int c) throws SQLException
+	{ inner.updateCharacterStream(a, b, c); }
+	
+	public synchronized void updateCharacterStream(String a, Reader b, int c) throws SQLException
+	{ inner.updateCharacterStream(a, b, c); }
+	
+	public synchronized void updateObject(String a, Object b) throws SQLException
+	{ inner.updateObject(a, b); }
+	
+	public synchronized void updateObject(int a, Object b) throws SQLException
+	{ inner.updateObject(a, b); }
+	
+	public synchronized void updateObject(int a, Object b, int c) throws SQLException
+	{ inner.updateObject(a, b, c); }
+	
+	public synchronized void updateObject(String a, Object b, int c) throws SQLException
+	{ inner.updateObject(a, b, c); }
+	
+	public synchronized void insertRow() throws SQLException
+	{ inner.insertRow(); }
+	
+	public synchronized void updateRow() throws SQLException
+	{ inner.updateRow(); }
+	
+	public synchronized void deleteRow() throws SQLException
+	{ inner.deleteRow(); }
+	
+	public synchronized void refreshRow() throws SQLException
+	{ inner.refreshRow(); }
+	
+	public synchronized void cancelRowUpdates() throws SQLException
+	{ inner.cancelRowUpdates(); }
+	
+	public synchronized void moveToInsertRow() throws SQLException
+	{ inner.moveToInsertRow(); }
+	
+	public synchronized void moveToCurrentRow() throws SQLException
+	{ inner.moveToCurrentRow(); }
+	
+	public synchronized Statement getStatement() throws SQLException
+	{ return inner.getStatement(); }
+	
+	public synchronized Blob getBlob(String a) throws SQLException
+	{ return inner.getBlob(a); }
+	
+	public synchronized Blob getBlob(int a) throws SQLException
+	{ return inner.getBlob(a); }
+	
+	public synchronized Clob getClob(String a) throws SQLException
+	{ return inner.getClob(a); }
+	
+	public synchronized Clob getClob(int a) throws SQLException
+	{ return inner.getClob(a); }
+	
+	public synchronized void updateRef(String a, Ref b) throws SQLException
+	{ inner.updateRef(a, b); }
+	
+	public synchronized void updateRef(int a, Ref b) throws SQLException
+	{ inner.updateRef(a, b); }
+	
+	public synchronized void updateBlob(String a, Blob b) throws SQLException
+	{ inner.updateBlob(a, b); }
+	
+	public synchronized void updateBlob(int a, Blob b) throws SQLException
+	{ inner.updateBlob(a, b); }
+	
+	public synchronized void updateClob(int a, Clob b) throws SQLException
+	{ inner.updateClob(a, b); }
+	
+	public synchronized void updateClob(String a, Clob b) throws SQLException
+	{ inner.updateClob(a, b); }
+	
+	public synchronized void updateArray(String a, Array b) throws SQLException
+	{ inner.updateArray(a, b); }
+	
+	public synchronized void updateArray(int a, Array b) throws SQLException
+	{ inner.updateArray(a, b); }
+	
+	public synchronized Object getObject(int a) throws SQLException
+	{ return inner.getObject(a); }
+	
+	public synchronized Object getObject(String a, Map b) throws SQLException
+	{ return inner.getObject(a, b); }
+	
+	public synchronized Object getObject(String a) throws SQLException
+	{ return inner.getObject(a); }
+	
+	public synchronized Object getObject(int a, Map b) throws SQLException
+	{ return inner.getObject(a, b); }
+	
+	public synchronized boolean getBoolean(int a) throws SQLException
+	{ return inner.getBoolean(a); }
+	
+	public synchronized boolean getBoolean(String a) throws SQLException
+	{ return inner.getBoolean(a); }
+	
+	public synchronized byte getByte(String a) throws SQLException
+	{ return inner.getByte(a); }
+	
+	public synchronized byte getByte(int a) throws SQLException
+	{ return inner.getByte(a); }
+	
+	public synchronized short getShort(String a) throws SQLException
+	{ return inner.getShort(a); }
+	
+	public synchronized short getShort(int a) throws SQLException
+	{ return inner.getShort(a); }
+	
+	public synchronized int getInt(String a) throws SQLException
+	{ return inner.getInt(a); }
+	
+	public synchronized int getInt(int a) throws SQLException
+	{ return inner.getInt(a); }
+	
+	public synchronized long getLong(int a) throws SQLException
+	{ return inner.getLong(a); }
+	
+	public synchronized long getLong(String a) throws SQLException
+	{ return inner.getLong(a); }
+	
+	public synchronized float getFloat(String a) throws SQLException
+	{ return inner.getFloat(a); }
+	
+	public synchronized float getFloat(int a) throws SQLException
+	{ return inner.getFloat(a); }
+	
+	public synchronized double getDouble(int a) throws SQLException
+	{ return inner.getDouble(a); }
+	
+	public synchronized double getDouble(String a) throws SQLException
+	{ return inner.getDouble(a); }
+	
+	public synchronized byte[] getBytes(String a) throws SQLException
+	{ return inner.getBytes(a); }
+	
+	public synchronized byte[] getBytes(int a) throws SQLException
+	{ return inner.getBytes(a); }
+	
+	public synchronized boolean next() throws SQLException
+	{ return inner.next(); }
+	
+	public synchronized URL getURL(int a) throws SQLException
+	{ return inner.getURL(a); }
+	
+	public synchronized URL getURL(String a) throws SQLException
+	{ return inner.getURL(a); }
+	
+	public synchronized int getType() throws SQLException
+	{ return inner.getType(); }
+	
+	public synchronized boolean previous() throws SQLException
+	{ return inner.previous(); }
+	
+	public synchronized void close() throws SQLException
+	{ inner.close(); }
+	
+	public synchronized String getString(String a) throws SQLException
+	{ return inner.getString(a); }
+	
+	public synchronized String getString(int a) throws SQLException
+	{ return inner.getString(a); }
+	
+	public synchronized Ref getRef(String a) throws SQLException
+	{ return inner.getRef(a); }
+	
+	public synchronized Ref getRef(int a) throws SQLException
+	{ return inner.getRef(a); }
+	
+	public synchronized Time getTime(int a, Calendar b) throws SQLException
+	{ return inner.getTime(a, b); }
+	
+	public synchronized Time getTime(String a) throws SQLException
+	{ return inner.getTime(a); }
+	
+	public synchronized Time getTime(int a) throws SQLException
+	{ return inner.getTime(a); }
+	
+	public synchronized Time getTime(String a, Calendar b) throws SQLException
+	{ return inner.getTime(a, b); }
+	
+	public synchronized Date getDate(String a) throws SQLException
+	{ return inner.getDate(a); }
+	
+	public synchronized Date getDate(int a) throws SQLException
+	{ return inner.getDate(a); }
+	
+	public synchronized Date getDate(int a, Calendar b) throws SQLException
+	{ return inner.getDate(a, b); }
+	
+	public synchronized Date getDate(String a, Calendar b) throws SQLException
+	{ return inner.getDate(a, b); }
+	
+	public synchronized boolean first() throws SQLException
+	{ return inner.first(); }
+	
+	public synchronized boolean last() throws SQLException
+	{ return inner.last(); }
+	
+	public synchronized Array getArray(String a) throws SQLException
+	{ return inner.getArray(a); }
+	
+	public synchronized Array getArray(int a) throws SQLException
+	{ return inner.getArray(a); }
+	
+	public synchronized boolean relative(int a) throws SQLException
+	{ return inner.relative(a); }
+	
+	public synchronized void updateTime(String a, Time b) throws SQLException
+	{ inner.updateTime(a, b); }
+	
+	public synchronized void updateTime(int a, Time b) throws SQLException
+	{ inner.updateTime(a, b); }
+	
+	public synchronized int findColumn(String a) throws SQLException
+	{ return inner.findColumn(a); }
+	
+	public synchronized int getRow() throws SQLException
+	{ return inner.getRow(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterStatement.java b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterStatement.java
new file mode 100644
index 0000000..a7fd352
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/filter/SynchronizedFilterStatement.java
@@ -0,0 +1,159 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.filter;
+
+import java.lang.String;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+
+public abstract class SynchronizedFilterStatement implements Statement
+{
+	protected Statement inner;
+	
+	public SynchronizedFilterStatement(Statement inner)
+	{ this.inner = inner; }
+	
+	public SynchronizedFilterStatement()
+	{}
+	
+	public synchronized void setInner( Statement inner )
+	{ this.inner = inner; }
+	
+	public synchronized Statement getInner()
+	{ return inner; }
+	
+	public synchronized SQLWarning getWarnings() throws SQLException
+	{ return inner.getWarnings(); }
+	
+	public synchronized void clearWarnings() throws SQLException
+	{ inner.clearWarnings(); }
+	
+	public synchronized void setFetchDirection(int a) throws SQLException
+	{ inner.setFetchDirection(a); }
+	
+	public synchronized int getFetchDirection() throws SQLException
+	{ return inner.getFetchDirection(); }
+	
+	public synchronized void setFetchSize(int a) throws SQLException
+	{ inner.setFetchSize(a); }
+	
+	public synchronized int getFetchSize() throws SQLException
+	{ return inner.getFetchSize(); }
+	
+	public synchronized int getResultSetHoldability() throws SQLException
+	{ return inner.getResultSetHoldability(); }
+	
+	public synchronized ResultSet executeQuery(String a) throws SQLException
+	{ return inner.executeQuery(a); }
+	
+	public synchronized int executeUpdate(String a, int b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public synchronized int executeUpdate(String a, String[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public synchronized int executeUpdate(String a, int[] b) throws SQLException
+	{ return inner.executeUpdate(a, b); }
+	
+	public synchronized int executeUpdate(String a) throws SQLException
+	{ return inner.executeUpdate(a); }
+	
+	public synchronized int getMaxFieldSize() throws SQLException
+	{ return inner.getMaxFieldSize(); }
+	
+	public synchronized void setMaxFieldSize(int a) throws SQLException
+	{ inner.setMaxFieldSize(a); }
+	
+	public synchronized int getMaxRows() throws SQLException
+	{ return inner.getMaxRows(); }
+	
+	public synchronized void setMaxRows(int a) throws SQLException
+	{ inner.setMaxRows(a); }
+	
+	public synchronized void setEscapeProcessing(boolean a) throws SQLException
+	{ inner.setEscapeProcessing(a); }
+	
+	public synchronized int getQueryTimeout() throws SQLException
+	{ return inner.getQueryTimeout(); }
+	
+	public synchronized void setQueryTimeout(int a) throws SQLException
+	{ inner.setQueryTimeout(a); }
+	
+	public synchronized void setCursorName(String a) throws SQLException
+	{ inner.setCursorName(a); }
+	
+	public synchronized ResultSet getResultSet() throws SQLException
+	{ return inner.getResultSet(); }
+	
+	public synchronized int getUpdateCount() throws SQLException
+	{ return inner.getUpdateCount(); }
+	
+	public synchronized boolean getMoreResults() throws SQLException
+	{ return inner.getMoreResults(); }
+	
+	public synchronized boolean getMoreResults(int a) throws SQLException
+	{ return inner.getMoreResults(a); }
+	
+	public synchronized int getResultSetConcurrency() throws SQLException
+	{ return inner.getResultSetConcurrency(); }
+	
+	public synchronized int getResultSetType() throws SQLException
+	{ return inner.getResultSetType(); }
+	
+	public synchronized void addBatch(String a) throws SQLException
+	{ inner.addBatch(a); }
+	
+	public synchronized void clearBatch() throws SQLException
+	{ inner.clearBatch(); }
+	
+	public synchronized int[] executeBatch() throws SQLException
+	{ return inner.executeBatch(); }
+	
+	public synchronized ResultSet getGeneratedKeys() throws SQLException
+	{ return inner.getGeneratedKeys(); }
+	
+	public synchronized void close() throws SQLException
+	{ inner.close(); }
+	
+	public synchronized boolean execute(String a, int b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public synchronized boolean execute(String a) throws SQLException
+	{ return inner.execute(a); }
+	
+	public synchronized boolean execute(String a, int[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public synchronized boolean execute(String a, String[] b) throws SQLException
+	{ return inner.execute(a, b); }
+	
+	public synchronized Connection getConnection() throws SQLException
+	{ return inner.getConnection(); }
+	
+	public synchronized void cancel() throws SQLException
+	{ inner.cancel(); }
+}
diff --git a/src/classes/com/mchange/v2/sql/junit/SqlUtilsJUnitTestCase.java b/src/classes/com/mchange/v2/sql/junit/SqlUtilsJUnitTestCase.java
new file mode 100644
index 0000000..168fe92
--- /dev/null
+++ b/src/classes/com/mchange/v2/sql/junit/SqlUtilsJUnitTestCase.java
@@ -0,0 +1,48 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.sql.junit;
+
+import java.sql.*;
+import junit.framework.*;
+import com.mchange.v2.sql.SqlUtils;
+
+public class SqlUtilsJUnitTestCase extends TestCase
+{
+    public void testGoodDebugLoggingOfNestedExceptions()
+    {
+	// this is only supposed to complete (in response to a bug where logging of 
+	// nested Exceptions was an infinite loop.
+	SQLException original = new SQLException("Original.");
+	SQLWarning nestedWarning = new SQLWarning("Nested Warning.");
+	original.setNextException( nestedWarning );
+	SqlUtils.toSQLException( original );
+    }
+
+    public static void main(String[] argv)
+    { 
+	junit.textui.TestRunner.run( new TestSuite( SqlUtilsJUnitTestCase.class ) ); 
+	//junit.swingui.TestRunner.run( SqlUtilsJUnitTestCase.class ); 
+	//new SqlUtilsJUnitTestCase().testGoodDebugLoggingOfNestedExceptions();
+    }
+}
\ No newline at end of file
diff --git a/src/classes/com/mchange/v2/util/DoubleWeakHashMap.java b/src/classes/com/mchange/v2/util/DoubleWeakHashMap.java
new file mode 100644
index 0000000..472f5c1
--- /dev/null
+++ b/src/classes/com/mchange/v2/util/DoubleWeakHashMap.java
@@ -0,0 +1,577 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.util;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+
+import com.mchange.v1.util.AbstractMapEntry;
+import com.mchange.v1.util.WrapperIterator;
+
+//TODO -- ensure that cleanCleared() gets called only once, even in methods implemented
+//        as loops. (cleanCleared() is idempotent, so the repeated calls are okay,
+//        but they're wasteful.
+
+/**
+ * <p>This class is <u>not</u> Thread safe.
+ * Use in single threaded contexts, or contexts where
+ * single threaded-access can be guaranteed, or
+ * wrap with Collections.synchronizedMap().</p>
+ * 
+ * <p>This class does not accept null keys or values.</p>
+ */
+public class DoubleWeakHashMap implements Map
+{
+    HashMap        inner;
+    ReferenceQueue keyQ = new ReferenceQueue();
+    ReferenceQueue valQ = new ReferenceQueue();
+    
+    CheckKeyHolder holder = new CheckKeyHolder();
+    
+    Set userKeySet = null;
+    Collection valuesCollection = null;
+    
+    public DoubleWeakHashMap()
+    { this.inner = new HashMap(); }
+    
+    public DoubleWeakHashMap(int initialCapacity)
+    { this.inner = new HashMap( initialCapacity ); }
+    
+    public DoubleWeakHashMap(int initialCapacity, float loadFactor)
+    { this.inner = new HashMap( initialCapacity, loadFactor ); }
+    
+    public DoubleWeakHashMap(Map m)
+    {
+        this();
+        putAll(m);
+    }
+
+    public void cleanCleared()
+    {
+        WKey wk;
+        while ((wk = (WKey) keyQ.poll()) != null)
+            inner.remove(wk);
+        
+        WVal wv;
+        while ((wv = (WVal) valQ.poll()) != null)
+            inner.remove(wv.getWKey());
+    }
+    
+    public void clear()
+    {
+        cleanCleared();
+        inner.clear();
+    }
+
+    public boolean containsKey(Object key)
+    {
+        cleanCleared();
+        try 
+            { return inner.containsKey( holder.set(key) ); }
+        finally
+            { holder.clear(); }
+    }
+
+    public boolean containsValue(Object val)
+    {
+        for (Iterator ii = inner.values().iterator(); ii.hasNext();)
+        {
+            WVal wval = (WVal) ii.next();
+            if (val.equals(wval.get()))
+                return true;
+        }
+        return false;
+    }
+
+    public Set entrySet()
+    {
+        cleanCleared();
+        return new UserEntrySet();
+    }
+
+    public Object get(Object key)
+    {
+        try
+        {
+            cleanCleared();
+            WVal wval = (WVal) inner.get(holder.set(key));
+            return (wval == null ? null : wval.get());
+        }
+        finally
+        { holder.clear(); }
+    }
+
+    public boolean isEmpty()
+    {
+        cleanCleared();
+        return inner.isEmpty();
+    }
+
+    public Set keySet()
+    {
+        cleanCleared();
+        if (userKeySet == null)
+            userKeySet = new UserKeySet();
+        return userKeySet;
+    }
+
+    public Object put(Object key, Object val)
+    {
+        cleanCleared();
+        WVal wout = doPut(key, val);
+        if (wout != null)
+            return wout.get();
+        else
+            return null;
+    }
+    
+    private WVal doPut(Object key, Object val)
+    {
+        WKey wk = new WKey(key, keyQ);
+        WVal wv = new WVal(wk, val, valQ);
+        return (WVal) inner.put(wk, wv);
+    }
+
+    public void putAll(Map m)
+    {
+       cleanCleared();
+       for (Iterator ii = m.entrySet().iterator(); ii.hasNext();)
+       {
+           Map.Entry entry = (Map.Entry) ii.next();
+           this.doPut( entry.getKey(), entry.getValue() );
+       }
+    }
+
+    public Object remove(Object key)
+    {
+        try
+        {
+            cleanCleared();
+            WVal wv = (WVal) inner.remove( holder.set(key) );
+            return (wv == null ? null : wv.get());
+        }
+        finally
+        { holder.clear(); }
+    }
+
+    public int size()
+    {
+        cleanCleared();
+        return inner.size();
+    }
+
+    public Collection values()
+    {
+        if (valuesCollection == null)
+            this.valuesCollection = new ValuesCollection();
+        return valuesCollection;
+    }
+    
+    final static class CheckKeyHolder
+    {
+        Object checkKey;
+        
+        public Object get()
+        {return checkKey; }
+        
+        public CheckKeyHolder set(Object ck)
+        { 
+            assert this.checkKey == null : "Illegal concurrenct use of DoubleWeakHashMap!";
+            
+            this.checkKey = ck; 
+            return this;
+        }
+        
+        public void clear()
+        { checkKey = null; }
+        
+        public int hashCode()
+        { return checkKey.hashCode(); }
+        
+        public boolean equals(Object o)
+        {
+            assert this.get() != null : "CheckedKeyHolder should never do an equality check while its value is null." ;
+            
+            if (this == o)
+                return true;
+            else if (o instanceof CheckKeyHolder)
+                return this.get().equals( ((CheckKeyHolder) o).get() );
+            else if (o instanceof WKey)
+                return this.get().equals( ((WKey) o).get() );
+            else
+                return false;
+        }
+    }
+    
+    final static class WKey extends WeakReference
+    {
+        int cachedHash;
+        
+        WKey(Object keyObj, ReferenceQueue rq)
+        {
+            super(keyObj, rq);
+            this.cachedHash = keyObj.hashCode();
+        }
+        
+        public int hashCode()
+        { return cachedHash; }
+        
+        public boolean equals(Object o)
+        {
+            if (this == o)
+                return true;
+            else if (o instanceof WKey)
+            {
+                WKey oo = (WKey) o;
+                Object myVal = this.get();
+                Object ooVal = oo.get();
+                if (myVal == null || ooVal == null)
+                    return false;
+                else
+                    return myVal.equals(ooVal);
+            }
+            else if (o instanceof CheckKeyHolder)
+            {
+                CheckKeyHolder oo = (CheckKeyHolder) o;
+                Object myVal = this.get();
+                Object ooVal = oo.get();
+                if (myVal == null || ooVal == null)
+                    return false;
+                else
+                    return myVal.equals(ooVal);
+            }
+            else
+                return false;
+        }
+    }
+    
+    final static class WVal extends WeakReference
+    {
+        WKey key;
+        WVal(WKey key, Object valObj, ReferenceQueue rq)
+        {
+            super(valObj, rq);
+            this.key = key;
+        }
+        
+        public WKey getWKey()
+        { return key; }
+    }
+    
+    
+    private final class UserEntrySet extends AbstractSet
+    {
+        private Set innerEntrySet()
+        {
+            cleanCleared();
+            return inner.entrySet();
+        }
+
+        public Iterator iterator()
+        {
+            return new WrapperIterator(innerEntrySet().iterator(), true)
+            {
+                protected Object transformObject(Object o)
+                {
+                    Entry innerEntry = (Entry) o;
+                    Object key = ((WKey) innerEntry.getKey()).get();
+                    Object val = ((WVal) innerEntry.getValue()).get();
+                    
+                    if (key == null || val == null)
+                        return WrapperIterator.SKIP_TOKEN;
+                    else
+                        return new UserEntry( innerEntry, key, val ); 
+                }
+            };
+        }
+        
+        public int size()
+        { return innerEntrySet().size(); }
+    }
+    
+    class UserEntry extends AbstractMapEntry
+    {
+        Entry innerEntry;
+        Object key;
+        Object val;
+
+        UserEntry(Entry innerEntry, Object key, Object val)
+        { 
+            this.innerEntry = innerEntry; 
+            this.key = key;
+            this.val = val;
+        }
+
+        public final Object getKey()
+        { return key; }
+
+        public final Object getValue()
+        { return val; }
+
+        public final Object setValue(Object value)
+        { return innerEntry.setValue( new WVal( (WKey) innerEntry.getKey() ,value, valQ) ); }
+    }    
+    
+    class UserKeySet implements Set
+    {
+        public boolean add(Object o)
+        {
+            cleanCleared();
+            throw new UnsupportedOperationException("You cannot add to a Map's key set.");
+        }
+
+        public boolean addAll(Collection c)
+        {
+            cleanCleared();
+            throw new UnsupportedOperationException("You cannot add to a Map's key set.");
+        }
+
+        public void clear()
+        { DoubleWeakHashMap.this.clear(); }
+
+        public boolean contains(Object o)
+        {
+            return DoubleWeakHashMap.this.containsKey(o);
+        }
+
+        public boolean containsAll(Collection c)
+        {
+            for (Iterator ii = c.iterator(); ii.hasNext();)
+                if (! this.contains(ii.next()))
+                    return false;
+            return true;
+        }
+
+        public boolean isEmpty()
+        { return DoubleWeakHashMap.this.isEmpty(); }
+
+        public Iterator iterator()
+        {
+            cleanCleared();
+            return new WrapperIterator(DoubleWeakHashMap.this.inner.keySet().iterator(), true)
+            {
+                protected Object transformObject(Object o)
+                {
+                    Object key = ((WKey) o).get();
+                    
+                    if (key == null)
+                        return WrapperIterator.SKIP_TOKEN;
+                    else
+                        return key; 
+                }
+            };
+        }
+
+        public boolean remove(Object o)
+        {
+            return (DoubleWeakHashMap.this.remove(o) != null);
+        }
+
+        public boolean removeAll(Collection c)
+        {
+            boolean out = false;
+            for (Iterator ii = c.iterator(); ii.hasNext();)
+                out |= this.remove(ii.next());
+            return out;
+        }
+
+        public boolean retainAll(Collection c)
+        {
+            //we implicitly cleanCleared() by calling iterator()
+            boolean out = false;
+            for (Iterator ii = this.iterator(); ii.hasNext();)
+            {
+                if (!c.contains(ii.next()))
+                {
+                    ii.remove();
+                    out = true;
+                }
+            }
+            return out;
+        }
+
+        public int size()
+        { return DoubleWeakHashMap.this.size(); }
+
+        public Object[] toArray()
+        { 
+            cleanCleared();
+            return new HashSet( this ).toArray(); 
+        }
+
+        public Object[] toArray(Object[] array)
+        {
+            cleanCleared();
+            return new HashSet( this ).toArray(array); 
+        }
+    }
+
+    class ValuesCollection implements Collection
+    {
+
+        public boolean add(Object o)
+        {
+            cleanCleared();
+            throw new UnsupportedOperationException("DoubleWeakHashMap does not support adding to its values Collection.");
+        }
+
+        public boolean addAll(Collection c)
+        {
+            cleanCleared();
+            throw new UnsupportedOperationException("DoubleWeakHashMap does not support adding to its values Collection.");
+        }
+
+        public void clear()
+        { DoubleWeakHashMap.this.clear(); }
+
+        public boolean contains(Object o)
+        { return DoubleWeakHashMap.this.containsValue(o); }
+
+        public boolean containsAll(Collection c)
+        {
+            for (Iterator ii = c.iterator(); ii.hasNext();)
+                if (!this.contains(ii.next()))
+                    return false;
+            return true;
+        }
+
+        public boolean isEmpty()
+        { return DoubleWeakHashMap.this.isEmpty(); }
+
+        public Iterator iterator()
+        {
+            return new WrapperIterator(inner.values().iterator(), true)
+            {
+                protected Object transformObject(Object o)
+                {
+                    Object val = ((WVal) o).get();
+                    
+                    if (val == null)
+                        return WrapperIterator.SKIP_TOKEN;
+                    else
+                        return val; 
+                }
+            };            
+        }
+
+        public boolean remove(Object o)
+        {
+            cleanCleared();
+            return removeValue(o);
+        }
+
+        public boolean removeAll(Collection c)
+        {
+            cleanCleared();
+            boolean out = false;
+            for (Iterator ii = c.iterator(); ii.hasNext();)
+                out |= removeValue(ii.next());
+            return out;
+        }
+
+        public boolean retainAll(Collection c)
+        {
+            cleanCleared();
+            return retainValues(c);
+        }
+
+        public int size()
+        { return DoubleWeakHashMap.this.size(); }
+
+        public Object[] toArray()
+        { 
+            cleanCleared();
+            return new ArrayList(this).toArray();
+        }
+
+        public Object[] toArray(Object[] array)
+        {
+            cleanCleared();
+            return new ArrayList(this).toArray(array);
+        }
+
+        private boolean removeValue(Object val)
+        {
+            boolean out = false;
+            for (Iterator ii = inner.values().iterator(); ii.hasNext();)
+            {
+                WVal wv = (WVal) ii.next();
+                if (val.equals(wv.get()))
+                {
+                    ii.remove();
+                    out = true;
+                }
+            }
+            return out;
+        }
+        
+        private boolean retainValues(Collection c)
+        {
+            boolean out = false;
+            for (Iterator ii = inner.values().iterator(); ii.hasNext();)
+            {
+                WVal wv = (WVal) ii.next();
+                if (! c.contains(wv.get()) )
+                {
+                    ii.remove();
+                    out = true;
+                }
+            }
+            return out;
+        }
+    }
+    
+    /*
+    public static void main(String[] argv)
+    {
+        DoubleWeakHashMap m = new DoubleWeakHashMap();
+        //Set keySet = new HashSet();
+        //Set valSet = new HashSet();
+
+        while (true)
+        {
+            System.err.println( m.inner.size() );
+
+            //if (Math.random() < 0.1f)
+            //    valSet.clear();
+            
+            Object key = new Object();
+            Object val = new long[100000];
+            //keySet.add(key);
+            //valSet.add(val);
+            m.put( key, val );
+        }
+    }
+    */
+}
diff --git a/src/classes/com/mchange/v2/util/ResourceClosedException.java b/src/classes/com/mchange/v2/util/ResourceClosedException.java
new file mode 100644
index 0000000..44d59d1
--- /dev/null
+++ b/src/classes/com/mchange/v2/util/ResourceClosedException.java
@@ -0,0 +1,67 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.util;
+
+import com.mchange.v2.lang.VersionUtils;
+
+public class ResourceClosedException extends RuntimeException
+{
+    //retaining 1.3.x compatability for now
+
+//     public ResourceClosedException(String msg, Throwable t)
+//     { super( msg, t ); }
+
+//     public ResourceClosedException(Throwable t)
+//     { super(t); }
+
+    Throwable rootCause;
+
+    public ResourceClosedException(String msg, Throwable t)
+    { 
+	super( msg ); 
+	setRootCause( t );
+    }
+
+    public ResourceClosedException(Throwable t)
+    { 
+	super(); 
+	setRootCause( t );
+    }
+
+    public ResourceClosedException(String msg)
+    { super( msg ); }
+
+    public ResourceClosedException()
+    { super(); }
+
+    public Throwable getCause()
+    { return rootCause; }
+
+    private void setRootCause( Throwable t )
+    {
+	this.rootCause = t;
+	if ( VersionUtils.isAtLeastJavaVersion14() )
+	    this.initCause( t );
+    }
+}
diff --git a/src/classes/com/mchange/v2/util/junit/DoubleWeakHashMapJUnitTestCase.java b/src/classes/com/mchange/v2/util/junit/DoubleWeakHashMapJUnitTestCase.java
new file mode 100644
index 0000000..9ffed70
--- /dev/null
+++ b/src/classes/com/mchange/v2/util/junit/DoubleWeakHashMapJUnitTestCase.java
@@ -0,0 +1,108 @@
+/*
+ * Distributed as part of c3p0 v.0.9.1.2
+ *
+ * Copyright (C) 2005 Machinery For Change, Inc.
+ *
+ * Author: Steve Waldman <swaldman at mchange.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1, as 
+ * published by the Free Software Foundation.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; see the file LICENSE.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+package com.mchange.v2.util.junit;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import com.mchange.v2.util.DoubleWeakHashMap;
+
+import junit.framework.TestCase;
+
+public class DoubleWeakHashMapJUnitTestCase extends TestCase
+{
+    public void testGetNeverAdded()
+    {
+        Map m = new DoubleWeakHashMap();
+        assertNull( m.get("foo") );
+    }
+    
+    public void testHardAdds()
+    {
+        Integer a = new Integer(1);
+        Integer b = new Integer(2);
+        Integer c = new Integer(3);
+        
+        String poop = new String("poop");
+        String scoop = new String("scoop");
+        String doop = new String("dcoop");
+        
+        Map m = new DoubleWeakHashMap();
+        m.put(a, poop);
+        m.put(b, scoop);
+        m.put(c, doop);
+        assertEquals("Size should be three, viewed via Map directly.", m.size(), 3);
+        assertEquals("Size should be three, viewed via keySet .", m.keySet().size(), 3);
+        assertEquals("Size should be three, viewed via values Collection.", m.values().size(), 3);
+        
+        int count = 0;
+        for (Iterator ii = m.keySet().iterator(); ii.hasNext();)
+        {
+            count += ((Integer) ii.next()).intValue();
+        }
+        assertEquals("Count should be six, viewed via values Collection.", count, 6);
+        
+        Integer d = new Integer(4);
+        m.put(d, poop);
+        m.values().remove(poop);
+        assertEquals("After removing a doubled value, size should be 2", m.size(), 2);
+    }
+    
+    public void testWeakness()
+    {
+        Integer a = new Integer(1);
+        Integer b = new Integer(2);
+        Integer c = new Integer(3);
+        
+        String poop = new String("poop");
+
+        Map m = new DoubleWeakHashMap();
+        m.put(a, poop);
+        m.put(b, new Object());
+        m.put(c, new Object());
+        
+        //race condition... b & c might already have been removed... but i doubt it
+        assertEquals("1) Weak values should not yet have been removed (but not guaranteed! sometimes fails without a defect!)", m.size(), 3);
+        
+        // we are relying that a full, synchronous GC occurs,
+        // which is not guaranteed in all VMs
+        System.gc();
+        
+        // let's see if we can force a deeper gc via a big array creation
+        byte[] bArray = new byte[1024 * 1024];
+        
+        assertEquals("2) Weak values should have been automatically removed (but not guaranteed! sometimes fails without a defect!)", m.size(), 1);
+        
+        m.put( new Object(), b);
+        
+        //race condition... b & c might already have been removed... but i doubt it
+        assertEquals("3) Weak key should not yet have been removed (but not guaranteed! sometimes fails without a defect!)", m.size(), 2);
+
+        System.gc();
+        // let's see if we can force a deeper gc via a big array creation
+        bArray = new byte[1024 * 1024];
+
+        assertEquals("4) Weak key should have been automatically removed (but not guaranteed! sometimes fails without a defect!)", m.size(), 1);
+    }
+}
diff --git a/src/codegen/com/mchange/v2/c3p0/impl/DriverManagerDataSourceBase.beangen-xml b/src/codegen/com/mchange/v2/c3p0/impl/DriverManagerDataSourceBase.beangen-xml
new file mode 100644
index 0000000..aa0e9f7
--- /dev/null
+++ b/src/codegen/com/mchange/v2/c3p0/impl/DriverManagerDataSourceBase.beangen-xml
@@ -0,0 +1,71 @@
+<beangen>
+  <package>com.mchange.v2.c3p0.impl</package>
+  <imports>
+     <general>java.util</general>
+     <specific>com.mchange.v2.c3p0.impl.AuthMaskingProperties</specific>
+     <specific>com.mchange.v2.c3p0.cfg.C3P0Config</specific>
+  </imports>
+  <modifiers>
+     <modifier>public</modifier>
+     <modifier>abstract</modifier>
+  </modifiers>
+  <output-class>DriverManagerDataSourceBase</output-class>
+  <extends>IdentityTokenResolvable</extends>
+  <properties> 
+    <property>
+       <variable><modifiers><modifier>protected</modifier></modifiers></variable>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+       <type>String</type>
+       <name>driverClass</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("driverClass", C3P0Defaults.driverClass())</default-value>
+       <bound/>
+    </property>
+    <property>
+       <variable><modifiers><modifier>protected</modifier></modifiers></variable>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+       <type>String</type>
+       <name>jdbcUrl</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("jdbcUrl", C3P0Defaults.jdbcUrl())</default-value>
+    </property>
+    <property>
+       <variable><modifiers><modifier>protected</modifier></modifiers></variable>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+       <type>Properties</type>
+       <name>properties</name>
+       <defensive-copy>
+           (Properties) AuthMaskingProperties.fromAnyProperties( properties )
+       </defensive-copy>
+       <default-value>new AuthMaskingProperties()</default-value>
+       <bound/>
+    </property>
+    <property>
+       <variable><modifiers><modifier>protected</modifier></modifiers></variable>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+       <type>String</type>
+       <name>description</name>
+    </property>
+    <property>
+       <type>String</type>
+       <name>identityToken</name>
+       <bound/> <!-- the C3P0PooledConnectionPoolManager will need to be reset when this changes -->
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <variable><modifiers><modifier>protected</modifier></modifiers></variable>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+       <type>String</type>
+       <name>factoryClassLocation</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("factoryClassLocation", C3P0Defaults.factoryClassLocation())</default-value>
+    </property>
+  </properties>
+</beangen>
+
+
+
+
diff --git a/src/codegen/com/mchange/v2/c3p0/impl/JndiRefDataSourceBase.beangen-xml b/src/codegen/com/mchange/v2/c3p0/impl/JndiRefDataSourceBase.beangen-xml
new file mode 100644
index 0000000..ec32c73
--- /dev/null
+++ b/src/codegen/com/mchange/v2/c3p0/impl/JndiRefDataSourceBase.beangen-xml
@@ -0,0 +1,53 @@
+<!-- DON'T FORGET TO CALL C3P0Registry.register( this ) AT CONSTRUCTION OF CONCRETE SUBCLASS -->
+
+<beangen>
+  <package>com.mchange.v2.c3p0.impl</package>
+  <imports>
+     <specific>java.util.Hashtable</specific>
+     <specific>javax.naming.Name</specific>
+     <specific>com.mchange.v2.c3p0.cfg.C3P0Config</specific>
+  </imports>
+  <output-class>JndiRefDataSourceBase</output-class>
+  <extends>IdentityTokenResolvable</extends>
+  <properties> 
+    <property>
+       <type>Object</type>
+       <name>jndiName</name>
+       <defensive-copy>
+           (jndiName instanceof Name ? ((Name) jndiName).clone() : jndiName /* String */)
+       </defensive-copy>
+       <constrained />
+       <bound />
+    </property>
+    <property>
+       <type>Hashtable</type>
+       <name>jndiEnv</name>
+       <defensive-copy>
+           (jndiEnv != null ? (Hashtable) jndiEnv.clone() : null)
+       </defensive-copy>
+       <bound />
+    </property>
+    <property>
+       <type>boolean</type>
+       <name>caching</name>
+       <default-value>true</default-value>
+       <bound />
+    </property>
+    <property>
+       <type>String</type>
+       <name>identityToken</name>
+       <bound/> <!-- the C3P0PooledConnectionPoolManager will need to be reset when this changes -->
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>factoryClassLocation</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("factoryClassLocation", C3P0Defaults.factoryClassLocation())</default-value>
+       <bound />
+    </property>
+  </properties>
+</beangen>
+
+
+
diff --git a/src/codegen/com/mchange/v2/c3p0/impl/PoolBackedDataSourceBase.beangen-xml b/src/codegen/com/mchange/v2/c3p0/impl/PoolBackedDataSourceBase.beangen-xml
new file mode 100644
index 0000000..4749a83
--- /dev/null
+++ b/src/codegen/com/mchange/v2/c3p0/impl/PoolBackedDataSourceBase.beangen-xml
@@ -0,0 +1,56 @@
+<!-- DON'T FORGET TO CALL C3P0Registry.register( this ) AT CONSTRUCTION OF CONCRETE SUBCLASS -->
+
+<beangen>
+  <package>com.mchange.v2.c3p0.impl</package>
+  <imports>
+     <specific>javax.sql.DataSource</specific>
+     <specific>javax.sql.ConnectionPoolDataSource</specific>
+     <specific>com.mchange.v2.c3p0.cfg.C3P0Config</specific>
+  </imports>
+  <output-class>PoolBackedDataSourceBase</output-class>
+  <extends>IdentityTokenResolvable</extends>
+  <properties> 
+    <property>
+       <type>ConnectionPoolDataSource</type>
+       <name>connectionPoolDataSource</name>
+       <bound/><!-- the C3P0PooledConnectionPoolManager will need to be reset when this changes -->
+       <constrained/><!-- ComboPooledDataSource requires the nested DataSources to be of c3p0 types -->
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>numHelperThreads</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("numHelperThreads", C3P0Defaults.numHelperThreads())</default-value>
+       <bound/><!-- the C3P0PooledConnectionPoolManager will need to be reset when this changes -->
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>identityToken</name>
+       <bound/> <!-- the C3P0PooledConnectionPoolManager will need to be reset when this changes -->
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>dataSourceName</name>
+       <default-value>null</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+
+    <property>
+       <type>String</type>
+       <name>factoryClassLocation</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("factoryClassLocation", C3P0Defaults.factoryClassLocation())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+  </properties> 
+</beangen>
+
+
+
+
diff --git a/src/codegen/com/mchange/v2/c3p0/impl/WrapperConnectionPoolDataSourceBase.beangen-xml b/src/codegen/com/mchange/v2/c3p0/impl/WrapperConnectionPoolDataSourceBase.beangen-xml
new file mode 100644
index 0000000..ac224d9
--- /dev/null
+++ b/src/codegen/com/mchange/v2/c3p0/impl/WrapperConnectionPoolDataSourceBase.beangen-xml
@@ -0,0 +1,252 @@
+<beangen>
+  <package>com.mchange.v2.c3p0.impl</package>
+  <imports>
+     <specific>javax.sql.DataSource</specific>
+     <specific>com.mchange.v2.c3p0.cfg.C3P0Config</specific>
+  </imports>
+  <modifiers>
+     <modifier>public</modifier>
+     <modifier>abstract</modifier>
+  </modifiers>
+  <output-class>WrapperConnectionPoolDataSourceBase</output-class>
+  <extends>IdentityTokenResolvable</extends>
+  <properties> 
+    <property>
+       <type>DataSource</type>
+       <name>nestedDataSource</name>
+       <bound/>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>maxStatements</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("maxStatements", C3P0Defaults.maxStatements())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>maxStatementsPerConnection</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("maxStatementsPerConnection", C3P0Defaults.maxStatementsPerConnection())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>initialPoolSize</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("initialPoolSize", C3P0Defaults.initialPoolSize())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>minPoolSize</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("minPoolSize", C3P0Defaults.minPoolSize())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>maxPoolSize</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("maxPoolSize", C3P0Defaults.maxPoolSize())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>maxAdministrativeTaskTime</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("maxAdministrativeTaskTime", C3P0Defaults.maxAdministrativeTaskTime())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>maxIdleTimeExcessConnections</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("maxIdleTimeExcessConnections", C3P0Defaults.maxIdleTimeExcessConnections())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>maxConnectionAge</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("maxConnectionAge", C3P0Defaults.maxConnectionAge())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>unreturnedConnectionTimeout</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("unreturnedConnectionTimeout", C3P0Defaults.unreturnedConnectionTimeout())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>idleConnectionTestPeriod</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("idleConnectionTestPeriod", C3P0Defaults.idleConnectionTestPeriod())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>maxIdleTime</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("maxIdleTime", C3P0Defaults.maxIdleTime())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>propertyCycle</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("propertyCycle", C3P0Defaults.propertyCycle())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>acquireIncrement</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("acquireIncrement", C3P0Defaults.acquireIncrement())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>acquireRetryAttempts</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("acquireRetryAttempts", C3P0Defaults.acquireRetryAttempts())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>acquireRetryDelay</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("acquireRetryDelay", C3P0Defaults.acquireRetryDelay())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>automaticTestTable</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("automaticTestTable", C3P0Defaults.automaticTestTable())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>connectionTesterClassName</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("connectionTesterClassName", C3P0Defaults.connectionTesterClassName())</default-value>
+       <constrained/>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>connectionCustomizerClassName</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("connectionCustomizerClassName", C3P0Defaults.connectionCustomizerClassName())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>boolean</type>
+       <name>debugUnreturnedConnectionStackTraces</name>
+       <default-value>C3P0Config.initializeBooleanPropertyVar("debugUnreturnedConnectionStackTraces", C3P0Defaults.debugUnreturnedConnectionStackTraces())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>boolean</type>
+       <name>testConnectionOnCheckout</name>
+       <default-value>C3P0Config.initializeBooleanPropertyVar("testConnectionOnCheckout", C3P0Defaults.testConnectionOnCheckout())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>boolean</type>
+       <name>testConnectionOnCheckin</name>
+       <default-value>C3P0Config.initializeBooleanPropertyVar("testConnectionOnCheckin", C3P0Defaults.testConnectionOnCheckin())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>preferredTestQuery</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("preferredTestQuery", C3P0Defaults.preferredTestQuery())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>boolean</type>
+       <name>autoCommitOnClose</name>
+       <default-value>C3P0Config.initializeBooleanPropertyVar("autoCommitOnClose", C3P0Defaults.autoCommitOnClose())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>boolean</type>
+       <name>forceIgnoreUnresolvedTransactions</name>
+       <default-value>C3P0Config.initializeBooleanPropertyVar("forceIgnoreUnresolvedTransactions", C3P0Defaults.forceIgnoreUnresolvedTransactions())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>int</type>
+       <name>checkoutTimeout</name>
+       <default-value>C3P0Config.initializeIntPropertyVar("checkoutTimeout", C3P0Defaults.checkoutTimeout())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>boolean</type>
+       <name>breakAfterAcquireFailure</name>
+       <default-value>C3P0Config.initializeBooleanPropertyVar("breakAfterAcquireFailure", C3P0Defaults.breakAfterAcquireFailure())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>boolean</type>
+       <name>usesTraditionalReflectiveProxies</name>
+       <default-value>C3P0Config.initializeBooleanPropertyVar("usesTraditionalReflectiveProxies", C3P0Defaults.usesTraditionalReflectiveProxies())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>overrideDefaultUser</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("overrideDefaultUser", C3P0Defaults.overrideDefaultUser())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>overrideDefaultPassword</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("overrideDefaultPassword", C3P0Defaults.overrideDefaultPassword())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>userOverridesAsString</name>
+       <default-value>C3P0Config.initializeUserOverridesAsString()</default-value>
+       <constrained/>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>identityToken</name>
+       <bound/> <!-- the C3P0PooledConnectionPoolManager will need to be reset when this changes -->
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+    <property>
+       <type>String</type>
+       <name>factoryClassLocation</name>
+       <default-value>C3P0Config.initializeStringPropertyVar("factoryClassLocation", C3P0Defaults.factoryClassLocation())</default-value>
+       <getter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></getter>
+       <setter><modifiers><modifier>public</modifier><modifier>synchronized</modifier></modifiers></setter>
+    </property>
+  </properties>
+</beangen>
+
+
+
+
diff --git a/src/dist-static/CHANGELOG b/src/dist-static/CHANGELOG
new file mode 100644
index 0000000..475e17f
--- /dev/null
+++ b/src/dist-static/CHANGELOG
@@ -0,0 +1,1179 @@
+c3p0-0.9.1.2
+	-- Fixed a variety of latent, hidden bugs discovered by "findbugs". Many thanks to Carsten Heyl
+	   for running this utility and reporting the issues.
+	-- Added lastAcquisitionFailureDefaultUser to ComboPooledDataSource's TO_STRING_IGNORE_PROPS,
+	   as some moments' Exception doesn't belong in a DataSource's toString(), and as trying to
+	   find this when stringifying close()ed DataSources provoked an infinte recursion (as an Exception
+	   was triggered trying to get the last failure info, which tries to include the stringified
+	   DataSource in its message, again provoking an Exception, ad infinitum.) Many thanks to Santiago 
+	   Aguiar for finding and diagnosing this problem.
+	-- Added better documentation of the com.mchange.v2.c3p0.cfg.xml configuration property.
+	   [Thanks to Legolas Wood for calling attention to this shortcoming.]
+	-- Calling getHoldability() to find the default Connection holdability provokes an Error
+	   on some DB2 drivers. Added a "careful" check which catches and works around this. 
+	   [Thanks to Lari Hotari for finding the problem, and providing a solution!] 
+	-- Refactored NewPooledConnection to not call event multicaster methods while holding
+	   the NewPooledConnection's lock. Holding locks while firing events is an old-fashioned
+	   way of provoking deadlocks, and it turns out c3p0 was not immune. Whoops! Another
+	   embarrassing one. Many thanks to Rhythm Tyagi and Pappu Sharadavani for finding this
+	   deadlock! 
+c3p0-0.9.1.1
+	-- Marked variable "logger" in Log4jMLogger. Rare null pointer exceptions apparently
+	   result from a stale value of the previously set variable. Did the same for
+	   Log4jMLogger, although no analoguous issue has thus far been reported. [Thanks to 
+	   Noa Resare and Denis on sourceforge for calling attention to this issue.]
+	-- Hid the attribute "properties" from access via JMX, since it often contains password
+	   information. I don't think properties is particularly useful via JMX, and this seems
+	   like a better solution than returning properties with a hidden/modified password
+	   property, since users might break things unintuitively be resetting a property to
+	   a value that, according to the mbean, was the original value. [Thanks to John Sumsion
+	   for calling attention to this issue.]
+    -- Fixed bug whereby setting any positive unreturnedConnectionTimeout forced c3p0's default 
+       effectivePropertyCycle down to 1 second. [Many thanks to Luke Dang for tracking down
+       this bug.]
+    -- Fixed an embarrassing bug whereby c3p0 was reflective testing for methods (setHoldability 
+       and setReadOnly) whose argument types are primitive int and boolean, by looking for 
+       Integer.class instead of int.class and Boolean.class instead of boolean.class. [Many
+       thanks to John Kristian for tracking down this subtle, and embarrassing issue very
+       precisely and suggesting the fix.]
+c3p0-0.9.1
+	-- Modified logging of uncleared SQLWarnings, so that warnings are emitted at INFO rather
+	   than WARNING, and via a dedicated logger [com.mchange.v2.c3p0.SQLWarnings], so that users
+	   can easily turn this behavior off. [Thanks to Oleksandr Alesinskyy for calling attention
+	   to the annoyingness of unconditionally logging all of this at WARNING.]
+c3p0-0.9.1-pre12a
+    -- Fixed a NullPointerException in DoubleWeakHashMap that showed up thanks to the newly
+       implemented canonicalization of Jdk14MLog instances. [Many thanks to Carlos Cajina for
+       finding and reporting this bug.]
+c3p0-0.9.1-pre12
+	-- Modified Jdk14MLog to canonicalize named MLogger instances. In doing so, put the getLogger()
+	   method behind a synchronization barrier, which hopefully will resolve an odd visibility issue
+	   bug reported by a user (wherein the underlying jdk Logger appeared occasionally to be null
+	   in threads that might have initialized prior to the logger) [Thanks to Denis on Sourcefore
+	   for reporting this subtle and interesting issue.]
+	-- Added a lastAcquisitionFailure property to PooledDataSource (and the JMX view thereof).
+	   Thanks to Troy Whittier for the suggestion, and the patch!
+	-- SQL Warnings present at Connection acquisition, or uncleared upon Connection checkin
+	   are now logged (at WARNING level) and cleared prior to being made available as part
+	   of the pool. This is necessary to sustain the invariant that all pooled Connections
+	   are identical and interchangible; warnings are Connection state that must be managed
+	   and homogenized. [Thanks to Naxe on the hibernate forums for calling attention to this
+	   issue!]
+	-- Resolved a rare NullPointerException which occurs when a Statement cache is closed
+	   beneath an active, checked out Connection. A client tries to prepare a Statement,
+	   but the closed Statement cache throws an NPE. Resolved this by having the closed
+	   Statement cache throw a ResourceClosedException, and having c3p0's proxy Statement
+	   recover by generating an uncached PreparedStatement. [Thanks to Sven / ford_perfect
+	   on sourceforge for finding and calling attention to this issue.]
+    -- Added c3p0 version to C3P0RegistryManagerMBean [Thanks to Sven / ford_perfect
+       on sourceforge for the suggestion.]
+c3p0-0.9.1-pre11
+	-- Documentation updates, and style-sheet fixes for FireFox.
+	
+	-- Despite the ugliness, added logic to prepend a VMID to identity
+	   tokens generated for c3p0 DataSources. See the docs (Appendix A,
+	   other configuration properties) for more information. Though there's
+	   little harm in the longer, uglier identityTokens, users who dislike
+	   them can turn them off using the property com.mchange.v2.c3p0.VMID.
+
+	-- Wrote ScatteringAcquireTask, which represents a significant change to
+	   how c3p0's underlying resource pool deals with Connections acquisition
+	   failures. c3p0 is designed to retry Connection acquisition up to 
+	   "c3p0.acquireRetryAttempts" times (30 by default) with a delay of
+	   "c3p0.acquireRetryDelay" milliseconds (1000 by default) between tries.
+	   Should no Connection be successfully acquired after acquireRetryAttempts,
+	   c3p0 gives up, logs an error, and frees clients waiting to check out
+	   a Connection with an Exception (declaring the pool permanently broken
+	   iff c3p0.breakOnAcquireFailure is set to true).
+
+	   Connection acquisition is performed asynchronously by c3p0's thread
+	   pool. Under the old implementation, when database Connections could
+	   not be acquired, a single task dispatched to the thread pool would
+	   hog a thread for the full length of the cycle -- 30 seconds by 
+	   default -- spending most of that time sleeping between retries.
+	   Under the new implementation, each acquisition attempt is its own
+	   separate task, and if an acquire attempt fails, a timer is used to
+	   schedule a retry adter acquireRetryDelay without hogging any thread.
+
+	   I think the new implementation is unambiguously superior to the old,
+	   as it permits clients to use smaller thread pools (c3p0.numHelperThreads) 
+	   and diminishes the likelihood of apparent thread pool deadlocks.
+	   However, it is too significant a change to introduce this late
+	   in the "mature" c3p0-0.9.1 series, so the old implementation is enabled 
+	   by default until the c3p0-0.9.2 development cycle begins. 
+
+	   TO USE THE NEW IMPLEMENTATION WITH c3p0-0.9.1, set...
+
+	     com.mchange.v2.resourcepool.experimental.useScatteredAcquireTask=true
+
+	   in a c3p0.properties file or as a system property. Users are strongly 
+	   encouraged to set this property. You'll see better resource utilization,
+	   and I'll get to hear issue reports if I've screwed anything up in
+	   the implementation.
+	-- Modified BasicResourcePool so that checkin (and removal) of resources
+	   is always synchronous if the pool is marked broken. [Otherwise the
+	   check-ins to a closed or broken pool may fail, as they try to use an 
+	   already close()ed asynchronous task runner.]
+	-- Modified BasicResourcePool.AcquireTask so that an interrupt always
+	   forces the whole task to end, not just a single acquisition
+	   attempt.
+	-- Exposed numThreadsAwaitingCheckout in PooledDataSource interface
+	   and its associated MBean. [Thanks to John Sumsion for suggesting
+	   this property.]
+	-- Prevented password from being exposed via 
+	   DynamicPooledDataSourceManagerMBean. [Thanks to John Sumsion
+	   for calling attention to this oversight!]
+	-- Fixed an error whereby DataSources.pooledDataSource( ... ) methods
+	   failed to properly set configuration properties defined on
+	   PoolBackedDataSource [e.g. numHelperThreads]. Many thanks to
+	   John Sumsion for noticing the problem and sending the fix!
+	-- Fixed AbstractConnectionCustomizer to implement (with no-op) methods
+	   the current definition of the ConnectionCustomizer interface. (Failed
+	   to update after modifying an early version of this interface.)
+	-- Modified NewPooledConnection to only collect stack trace information
+	   on close() when we are debugging at FINEST or DEBUG (log4j) level.
+	   Previously, stack trace information was collected anytime a 
+	   c3p0 PooledConnection was close()ed. This was unnecessary, although
+	   the performance cost was probably small to negligible, since 
+	   PooledConnections are typically reused many times before they are 
+	   close()ed. Stack trace information is still captured when 
+	   PooledConnections are destroyed due to an error, rather than an
+	   ordinary call to close().
+	-- Hid the getLastXXXFailure(user, password) from the operations list of 
+	   DynamicPooledDataSourceManagerMBean. The stack traces of failures
+	   are visible via the sampleLastXXXFailureStackTrace(); there's no
+	   need to expose the Throwbales as well via JMX.
+	-- Added convenience methods getNumPooledDataSources() and getNumPoolsAllDataSources()
+	   to C3P0Registry and the related MBean. [Thanks to lukeda on hibernate
+	   forums for calling attention to the need for a pool count, since
+	   DataSources can wrap multiple pools associated with different
+	   authentications.]
+	-- Eliminated the "trial Connection acquisition" added to c3p0-0.9.1-pre10 
+           when PooledDataSources construct the pool with their default authentification
+	   information. Effectively we trust that the default auntentification is
+	   good, while we distrust client-specified (or JMX-queried) username/password
+	   combinations, and perform the extra check. This is a compromise, as users 
+	   who frequently create and destroy DataSources don't like the extra overhead 
+	   of the trial Connection acquisition, but we really want to avoid the initialization 
+	   of bad pools when users misspecify authentification information. We've also prevented
+	   mere queries of pool status from triggering the initialization of a 
+	   non-default-authentification pool. Only calls to getConnection(user, password)
+	   will provoke the initialization of a non-default-auth pool. Querying
+	   a non-default pool -- e.g. getNumBusyConnections( "steve", "test" ) -- will
+	   throw an Exception if no Conections have ever been requested for ("steve", "test")
+	   rather than initializing a new pool. [Thanks to Jiji Sasidharan for calling
+	   attention to this issue.]
+        -- Finally rationalized C3P0Registry's Object retention policy. C3P0Registry
+           retains references to all C3P0 DataSources, to provide a central
+           point of client access for users, and to ensure that multiple JNDI
+           lookups within a single VM result in the same DataSource instance.
+           This is both intuitive, and avoids multiplying Threads and pools.
+           In previous versions of c3p0, hard references were retained indefinitely.
+           Closed DataSources hold no non-memory resources, and are small Objects,
+           but in unusual scenarios wherein very many c3p0 DataSources are created
+           and destroyed within the lifetime of a single VM, this amounts to a
+           memory leak. C3P0Registry now retains hard references to DataSources
+           so long as they have not been close()ed (or DataSources.destroy()ed).
+           When they have been close()ed, only weak references to c3p0 DataSources
+           persist, and they are eligible for garbage collection. [Thanks to 
+           Jeremy Grodberg for calling attention to this as a real-world issue.]
+	-- Removed some of the new pool stat params which distractingly appear in
+	   ComboPooledDataSource's toString() method (and c3p0 pools' config dump).
+c3p0-0.9.1-pre10
+	-- Exposed statement pool stats via PooledDataSource interface and MBean.
+	   Added accessors to the statement cache, pool, pool manager, and 
+	   AbstractPoolBackedDataSource.
+	-- Fixed a bug whereby multiple near-simultaneous Connection requests failed
+	   to provoke sufficient pool growth/Connection acquisition. This is a bug
+	   reintroduced in c3p0-0.9.1-pre4, and is a duplicate of an early issue,
+	   resolved under the old pool size management scheme. c3p0 must (and does)
+	   keep track not only of its current size and pending growth, but also the 
+	   number of potentially unserved recent Connection requests. c3p0-0.9.1-pre4
+	   thru c3p0-0.9.1-pre9 failed to make use of the incoming request count when
+	   deciding whether and how much to grow the pool. Many thanks to John Kristian
+	   for calling attention to this subtle issue.
+	-- Exposed information about Connection test failures (failure count of
+	   each type of test, stack trace of last failure and last failure of each
+	   type) via the PooledDataSource interface and its assiciated MBean. 
+	-- Fixed a rare situation where a Connection test on check-out could
+	   be performed by a Thread holding the pool's lock, if an initial
+	   attempt to checkout a Connection failed and a second checkout attempt
+	   is initiated internally. The recursive checkout is now performed
+	   without the pool's lock.
+	-- Established a consisent policy re logging of Connection test failures.
+	   They are now logged at FINE level (DEBUG under log4j). Previously tests
+	   on checkin and checkout were logged at FINE, but idle tests were logged
+	   at INFO. Is it worth letting this be configurable, as some users may prefer
+	   to see test failures under normal operation?
+	-- Modified C3P0PooledConnectionPoolManager to perform a trial Connection
+	   acquisition before trying to establish a pool, as pools established
+	   on bad database or authentification information churn indefinitely trying
+	   to acquire minPoolSize Connections, clogging up the thread pool and
+	   sometimes leading to "APPARENT DEADLOCKS" if users touch a pool with
+	   bad authentification information.
+	-- Defined UnifiedConnectionTester (and AbstractConnectionTester) so that
+	   ConnectionTesters can provide any Exception associated with a failure,
+	   which will show up as the "root cause" of the Exception eventually
+	   thrown to signal the failure.
+	-- Added logging at WARNING level when a Connection tester reports the
+	   database is invalid, triggering a reset of the Connection pool.
+c3p0-0.9.1-pre9
+	-- Updated the documentation. It is now fully current.
+	-- Changed the name of the jboss configuration mbean from 
+	   com.mchange.v2.c3p0.mbean.C3P0PooledDataSource to com.mchange.v2.c3p0.jboss.C3P0PooledDataSource,
+	   in order to distinguish the jboss configuration stuff from the normal management
+	   beans, which really are quite different. The old com.mchange.v2.c3p0.mbean.C3P0PooledDataSource
+	   still exists, so that existing users don't see anything break, but only the new version will
+	   be updated to support new parameters.
+	-- Added a means by which users can suppress JMX MBean registration if they
+	   wish. Setting the property com.mchange.v2.c3p0.management.ManagementCoordinator
+	   to com.mchange.v2.c3p0.management.NullManagementCoordinator will
+	   prevent mbean registration. (Advanced users can also use this to install
+	   their own implementations of the ManagementCoordinator interface, though
+	   I doubt many people will find this useful.)
+	-- Fixed infinitely loop in ComboPooledDataSource.setFactoryClassLocation( ... ) [introduced with
+	   the reorganization of ComboPooledDataSource to inherit from AbstractPoolBackedDataSource (?)]
+ 	-- Added some code to warn on common spelling errors of c3p0.properties
+ 	   and c3p0-config.xml (Users have frequently mistaken the digit 0 for
+ 	   the letter O in c3p0.)
+    -- Wrote a DynamicMBean for PooledDataSources. It seems to work, 
+       covers both ComboPooledDataSource and "wrapping" PoolBackedDataSources, 
+       and should cover new config parameters and accessors automatically.
+	-- More refinements to connection test logic. We test proxy Connections
+	   when there's a statement cache and a test query (to save time, since
+	   the test query is likely cached), we test physical connections if we'll
+	   need to do the slow, default test (statement caching doesn't help), or
+	   if there is no statement cache.
+	-- Removed the stub servlet I'd previously added. Although it jdk1.5+, the
+	   JMX approach is a better one for monitoring and modifying c3p0 pools at
+	   runtime, and I'm going to focus on that for now. (If there's a lot of
+	   clamor for it, I'll add back and flesh out the servlet. It just doesn't
+	   seem like a great use of my time for now.)
+	-- Modified build to create a separate jdk1.3 compatable jar by stripping
+	   source of lines beginning with "assert". I want to start using assertions,
+	   but will keep them to recognizable, strippable one-liners.
+	-- Modified DriverManagerDataSource to handle its driverClass parameter
+	   in a less fragile way. Rather than attempting to load the driverClass on
+	   property set, DriverManagerDataSource now lazily tries to load the class
+	   upon its first call to getConnection() after the property is initialized
+	   or updated. 
+c3p0-0.9.1-pre8
+	-- Fixed bug whereby jdbcUrl and driverClass were not initialized properly
+	   if set in config files rather than in code. [Thanks to Felipe Dominguez
+	   for calling attention to this problem!]
+	-- Changed AbstractPoolBackedDataSource from a non-public class in
+	   com.mchange.v2.c3p0 to a public class in com.mchange.v2.c3p0.impl to
+	   workaround fragilities associated with sun bug id #6342411, wherein
+	   reflective invocation of public methods on a non-public class fail,
+	   even when involed via a public base-class.
+	-- Fixed NullPointerException (introduced on -pre7) on initializing with
+	   a named config.
+	-- Continuing along the same lines, modified C3P0Registry and 
+	   C3P0ManagementCoordinator to unregister (both from C3P0Registry and
+	   associated MBeans on DataSource close. When all PooledDataSources
+	   are unregistered, the C3P0RegistryManager MBean unregisters as well.
+	-- Modified ActiveManagementCoordinator to check for, and unregister, the
+	   C3P0Registry mbean prior to registration, to prevent conflicts on
+	   undeploy/redepoloy cycle. [Thanks to Ben Groeneveld for calling attention
+	   to this problem, and suggesting the fix.]
+	-- Modified C3P0PooledConnectionPool to perform Connection tests on the raw
+	   physical Connection rather than the proxy Connection, when possible (i.e.
+	   when we're using a C3P0 PooledConnection implementation that we know how to
+	   get underneath of). This is better because 1) it's slightly faster; 2) it's
+	   significantly faster on failure, as C3P0 PooledConnections test Connections
+	   after a failure with statusOnException(), leading to an unnecessary 
+	   double-check; and 3) authors of custom ConnectionTesters can use vendor-specific
+	   API to test Connections if they wish.
+c3p0-0.9.1-pre7
+	-- Defined the ConnectionCustomizer interface as a hook through which clients 
+    	   can "set up" newly acquired or checked out connections (and clean up prior 
+	   to check-in or destruction). Added config param connectionCustomizerClassName.
+	-- Formerly unused config parameter propertyCycle now controls how frequently 
+	   c3p0 checks timestamps against expiration parameters (maxIdleTime, 
+	   maxIdleTimeExcessConnections, maxConnectionAge, unreturnedConnectionTimeout)
+	-- Added config parameters unreturnedConnectionTimeout and 
+	   debugUnreturnedConnectionStackTraces, reluctantly but by popular demand.
+	   If unreturnedConnectionTimeout is set, Connections will be automatically
+	   destroyed and removed from the pool if not returned in the given number
+	   of seconds. If debugUnreturnedConnectionStackTraces is also set, stack
+	   traces will be captured on check-out of any Connection, and the stack traces
+	   that were unreturned and cleaned up by the pool will be logged. Any clean-up
+	   of an unreturned Connection is logged at INFO level, in hopes of annoying
+	   people into cleaning up their code and close()-ing their Connections reliably.
+	-- Added config parameter maxIdleTimeExcessConnections. Connections in excess of
+	   the pool's minimum size will be expired out if they remain idle for longer
+	   than maxIdleTimeExcessConnections. [Thanks to Navin M. and Ben Groeneveld for 
+	   the suggestion.]
+	-- Added configuration parameter maxConnectionAge to limit the absolute age 
+	   of Connections (age in seconds from acquisition, rather than in seconds
+	   form last check-in).
+	-- Put more status accessors in ThreadPoolAsynchronousRunner and exposed
+	   thread pool state via JMX (PooledDataSourceManagerMBean).
+	-- Preliminary support for management via mbeans/jmx under jdk1.5+ is 
+	   implemented. (These mbeans are distinct from the mbean designed for 
+	   configuring c3p0 under jboss, and are in the package 
+	   com.mchange.c3p0.management.) Currently, pool statistics are instrumented, 
+	   as well as pool reset operations, but viewing and modifying the pool 
+	   configuration parameters is not supported. (I'll write a dynamic mbean to 
+	   capture this stuff; there are too many config properties I'm already 
+	   updating by hand in too many places. Later.) [Thanks to Ben Groeneveld for 
+	   the suggestion, including some nice sample code!]
+	-- Added configuration parameter maxAdministrativeTaskTime, which forces 
+	   interrupt() to be called on pool administrative tasks (Connection acquisition, 
+	   destruction and testing; prepared statement destruction) if these tasks seem 
+	   to hang. This is one strategy for dealing with database that hang and lead to
+	   deadlock messages and eventually large memory footprints. It's useful only if 
+	   the hanging operations respond to interrupt()s, not thread-monitor related 
+	   deadlocks (which hopefully don't ever happen). c3p0 should recover from any 
+	   Exceptions provoked by the interrupt() calls. (Hopefully improvements to
+	   GooGooStatementCache have rendered this parameter largely unnecessary by
+	   resolving the most common source of deadlocks.)
+	-- Major revision to GooGooStatementCache that may eliminate most APPARENT
+	   DEADLOCK messages. c3p0-0.9.0 introduced a complicated implementation
+	   of closeAll(), in order to ensure that Statement and Connection close did
+	   not happen simultaneously. Upon review, there was a significant bug in the
+	   implementation, and even when corrected, the strategy is itself prone to
+	   deadlocks should the ThreadPool become saturated with destroy tasks, which
+	   block awaiting statement remove tasks. Rewrote closeAll() as a simple
+	   synchronous method, rather than simulating synchrony with wait()/notifyAll(),
+	   but with care not to hold the StatementCache's lock during Statement.close()
+	   calls. There is a slight loss of asynchrony, as Statement.close() methods
+	   are executed sequentially rather than simultaneously, but as a practical
+	   matter, since the sequential destruction occurs asynchronously in the
+	   thread pool and is invisible to clients, both client-observed and over-all
+	   performance should not suffer. (It may even be improved, as the 0.9.0.x 
+	   strategy was complicated and had some overhead.) Note: This revision should
+	   also eliminate occasional ConcurrentModificationExceptions associated
+	   with the Statement cache.
+	-- Changed asynchronous tasks implemented as anonymous inner classes to named
+	   inner classes, so that users reviewing active and pending tasks (usually
+	   in trying to make sense of an APPARENT DEADLOCK) have a better idea of
+	   what's going on.	    
+	-- Major revision to GooGooStatementCache that may eliminate most APPARENT
+	   DEADLOCK messages. c3p0-0.9.0 introduced a complicated implementation
+	   of closeAll(), in order to ensure that Statement and Connection close did
+	   not happen simultaneously. Upon review, there was a significant bug in the
+	   implementation, and even when corrected, the strategy is itself prone to
+	   deadlocks should the ThreadPool become saturated with destroy tasks, which
+	   block awaiting statement remove tasks. Rewrote closeAll() as a simple
+	   synchronous method, rather than simulating synchrony with wait()/notifyAll(),
+	   but with care not to hold the StatementCache's lock during Statement.close()
+	   calls. There is a slight loss of asynchrony, as Statement.close() methods
+	   are executed sequentially rather than simultaneously, but as a practical
+	   matter, since the sequential destruction occurs asynchronously in the
+	   thread pool and is invisible to clients, both client-observed and over-all
+	   performance should not suffer. (It may even be improved, as the 0.9.0.x 
+	   strategy was complicated and had some overhead.)
+	-- Users still occasionally report seeing "APPARENT DEADLOCK" messages
+	   from ThreadPoolAsynchronousRunner. Previously, most reports had to
+	   do with cached PreparedStatements that hang when closed. Previous
+	   fixes (in 0.9.0) seemed to resolve these problems. Most new reports
+	   have to do with Connection acquisition or Connection close() tasks
+	   hanging indefinitely, with statement cacheing not necessarily involved.
+	   It's unclear whether these reports stem from a c3p0 bug, or rare 
+	   situations in which various JDBC driver hangs hang c3p0. (If the
+	   condition is temporary, c3p0 recovers after the "APPARENT DEADLOCK"
+	   warning. In any case, ThreadPoolAsynchronousRunner has been modified
+	   to provide much better debug information, particularly in jdk1.5 
+	   environments, where hung Thread stack traces are dumped. 
+	-- Modified the build file and refactored C3P0Config / C3P0ConfigUtils / C3P0ConfigXmlUtils
+	   to make sure that c3p0 still runs under jdk1.3, even though it only builds
+	   under jdk 1.5. JMX support is 1.5 only, and XML config depends on the
+	   availability of standard XML extensions under jdk 1.3. (This is going to get harder,
+	   as I hope to start using assertions. For the moment, assertions are
+	   commented out. I think I'll have ant filter away the assertions for 1.3
+	   builds and leave them in otherwise, based on a build property.
+	-- Significantly reorganized (hopefully simplified) of C3P0Registry and 
+	   ComboPooledDataSource. Much of the complication of all this is supporting 
+	   the most annoying, least used, but nevertheless very important feature
+	   of supporting JNDI lookups across JVM boundaries via Serialization or JNDI 
+	   References.
+	-- Reorganized ComboPooledDataSource to inherit from PoolBackedDataSource.
+	   In doing so, changed setConnectionPoolDataSource() of PoolBackedDataSource
+	   to throw a PropertyVetoException. (It is a "constrained property" now.)
+	   The very, very few users who call this method directly may have to update
+	   their code to handle the potential Exception. This change does not affect
+	   compatability for clients that use ComboPooledDataSource or who create
+	   PoolBackedDataSources via the c3p0's factories (e.g. DataSources).
+	-- Fixed bug whereby config parameter breakAfterAcquireFailure did not take 
+	   effect at the ResourcePool level.
+	-- Fixed bug whereby initialPoolSize parameter failed to take effect at the 
+	   ResourcePool level.
+	-- Fixed a bug whereby Connection tests on checkout held the pool's lock, 
+	   preventing other activity during the Connection test. [Thanks to John 
+	   Sumsion for calling attention to this subtle problem.]
+	-- ThreadPoolAsynchronousRunner has seen some minor efficiency improvements 
+	   (a task that calls interrupt() long-running tasks on post-deadlock blocked, 
+	   replaced threads no longer runs when there are no post deadlock blocked, 
+	   replaced threads).
+	-- Previous versions used a Stringified identityHashCode() as a unique ID
+	   for various C3P0 objects. This was not correct (and bit at least one
+	   user in practice), as identityHashCode()s are not guaranteed unique,
+	   and in practice are not on 64-bit VMs. c3p0 now checks identityHashCodes 
+	   to make sure they are not reused, and if seen before appends a count to
+	   keep unique IDs unique. [Many thanks to Prishan Makandurage for calling
+	   attention to this issue.]
+	-- Fixed fact that statusOnException() in NewPooledConnection() ignored
+	   preferredTestQuery parameter, always using the very slow default 
+	   connection test rather than the user-defined test. [Thanks to Andrea
+	   Luciano for calling attention to this issue.]
+	-- C3P0PooledConnection (part of the "traditionalReflectiveProxies"
+	   codebase that's largely been superceded, but that is still nominally
+	   supported) held a static reference to a ClassLoader, in some cases
+	   preventing the ClassLoader from being garbage collected (for example
+	   when webapp contexts, with context specific Classloaders are 
+	   undeployed by some app servers). This was entirely unnecessary, as 
+	   the cached reference was used exactly once in the lifecycle of the
+	   class. The reference to the ClassLoader is no longer stored. [Many
+	   thanks to Michael Fortin for very specifically tracking down this 
+	   subtle problem!]
+	-- Added logic to log individual acquisition failures at INFO level
+	   if acquireRetryAttempts <= 0. if num_acq_attempts <= 0, we try to 
+	   acquire forever, so the end-of-batch log message below will never 
+	   be triggered if there is a persistent problem so in this case, 
+	   it's better flag a higher-than-debug-level message for
+	   each failed attempt. [Thanks to Eric Crahen for calling attention 
+	   to this issue.]
+c3p0-0.9.1-pre6
+	-- Wrote a stub of a status-monitoring servlet. Not yet documented, or
+	   even usefully functional, but hopefully will be soon.
+	-- Updated documentation to include new config file format, including
+	   named and per-user config. Docs could still use some work, but the
+	   functionality is now described.
+	-- For consistency, the parameters user and password are now configurable
+	   via config files and System properties.
+	-- Modified DefaultC3P0ConfigFinder to properly give greater precedence to
+	   System properties than to XML-defined unspecified user, default config
+	   values.
+	-- Added WARNING logging of create SQL and original database Exception when
+	   creation of an automaticTestTable fails. [Thanks to Alexander Grivnin.]
+	-- Added logic so that the Exception assoicated with the last failed acquisition
+	   attempt in a round of attempts is logged along with the failure if the 
+	   acquisition attempts. [Thanks to Barthel Steckemetz and Patrick Eger for
+	   calling attention to this issue.]
+	-- Fixed a problem whereby modifying the config of a ComboPooledDataSource
+	   programmatically, while in use, causes previously checked-out Connections
+	   to be close()ed underneath the user. Changing configs midstream still 
+	   causes a complete resetting of the pool (because the pool holds most
+	   config params as immutable to avoid having to synchronized for every
+	   config param read), but old Connections from superceded pools will remain
+	   valid until they are rechecked into the pool. [Thanks to hhu for noticing
+	   this problem!]
+        -- Fixed a resource leak whereby Connections which could not be reset after
+	   client-initiated close() were properly "excluded" from future inclusion
+	   in the pool, but never checked back into the pool to be destroyed. (Excluded
+           resources are simply marked for destruction rather than reassimilation on
+           checkin.) [Many thanks to Levi Purvis for tracking this down!]
+c3p0-0.9.1-pre5a
+	-- "test-classpath" was erroneously specified in build.xml to include some
+	   extra jar files, which rendered undetectable (to my tests) the fact
+	   that com.mchange.v1.lang.BooleanUtils was not jarred up into 
+	   c3p0-0.9.1-pre5.jar as it needed to be. Very, very embarrrassing. [Thanks
+	   to Goksel Uyulmaz for calling attention to this gaffe.]
+c3p0-0.9.1-pre5
+	-- reorganized synchronization of getConnection() in DriverManagerDataSource, 
+	   so that a lock-up in one Connection acquisition attempt does not prevent 
+	   other threads from accessing the DataSource. [Thanks to Fabian Gonzalez
+	   for calling attention to this occasional deadlock.]
+c3p0-0.9.1-pre4
+	-- added overrideDefaultUser and overrideDefaultPassword parameters, and
+	   a pooledDataSource( ) factory method which specifies override 
+	   authentification info, regardless of the user and password properties
+	   set on the DataSource.
+	-- Reimplemented resource acquisition in BasicResourcePool so that each
+	   Connection acquisition is a separate asynchronous task, to avoid tying
+	   up the pool with very long, multiple acquisition tasks. Ended up being
+	   a total rewrite of pool size management. Pools should be much more
+	   conservative in acquiring new Connections. They won't grow to maxPoolSize
+	   as easily as before. This is a big change. We'll see how it holds up as 
+	   people beat the crap out of it.
+c3p0-0.9.1-pre3
+	-- Added some extra logging of resource destruction, to help debug cases
+	   where the resources are removed from the pool, but appear not to have
+	   been properly cleaned up.
+	-- Finished implementation of new C3P0Config, added constructor methods to 
+	   various DataSource implementations that accept a configName parameter for 
+	   named configurations, and modified the DataSources class to use the new
+	   configuration approach instead of PoolConfig wherever possible.
+c3p0-0.9.1-pre2
+	-- Continued implementation of new C3P0Config, including XML parsing
+	   and setting of default values. Still have to test XML config, and
+	   implement named and per-user configs.
+	-- Changed policy of BasicResourcePool to keep accidentally overacquired
+	   resources so long as the pool is kept below maxPoolSize, rather than
+	   immediately discarding Connections beyond the number we intended to
+	   acquire.
+	-- Fixed two issues whereby checking in resources to closed pools (both
+	   Connection and Statement pools) provoked Exceptions. Check-ins should
+	   succeed, even to close()ed pools, with resources silently destroyed if
+	   the pools ae closed. [Thanks to Chris Kessel for finding both of these
+	   issues.]
+	-- Modified to a one-statement-cache-per-pool model, rather than a 
+	   global-statement-cache-for-all-pools, consistent with the general
+	   philosophy that config parameters are on a per-auth-pool basis. 
+        -- Fixed a deadlock which could occur if a pool is closed at the same time as
+           one of its Connections is closed. One thread gets the pool's lock, then tries
+           to get a PooledConnections lock to close it. An ordinary client closes a
+           Connection, which acquires the PooledConnection's lock, and then tries to
+           check back in to the pool, requiring the pool's lock. Broke the deadlock by
+           making the destruction of resources on pool close asynchronous, by a Thread
+	   without the resource pool's lock.. [Many thanks to Roger Westerlund for 
+	   sending a full thread-stack dump capturing this deadlock!]
+	-- Completed transformation to one C3P0PooledConnectionPoolManager per
+	   PoolBackedDataSource model.
+c3p0-0.9.1-pre1
+	[This is a half-assed internal release... it compiles and works, but mostly
+	it's just a checkpoint as c3p0 undergoes significant transformations.]
+	-- Deprecated PoolConfig, and began implementation of new C3P0Config approach
+	   to configuration.
+	-- Partial transformation of C3P0PooledConnectionPoolManager from shared-manager
+	   for several DataSources model to simple one-pool-per-DataSource model enabled
+	   by canonicalization of DataSources.
+c3p0-0.9.0.4
+	-- In a subtle but major modification of BasicResourcePool, changed checkin/checkout
+	   behavior from FIFO to LIFO to increase the likelihood that unnecessary Connections
+	   pooled at peak times idle long enough to expire. [Many thanks to Vlad Ilyushchenko
+	   and hontvari on sourceforge for noticing this important issue and suggesting the 
+	   fix.]
+c3p0-0.9.0.3
+	-- Added code to mbean C3P0PooledDataSource to automatically create JNDI
+	   subcontexts if a JNDI name is specified for which the full path has not
+           been created (analogous to mkdir -p in UNIX). [Thanks to David D. Kilzer
+	   for noticing the issue, and for submitting the fix!]
+	-- Added logic to GooGooStatementCache to ensure that multiple threads do not
+	   try to close the same PreparedStatement, in order to try to resolve longstanding
+	   PreparedStatement.close() freeze issues. [Thanks to cardgames on sourceforge for
+	   noticing that the freezes seem to occur when several threads are trying to close()
+	   just one statement!]
+	-- Added some checks in DriverManagerDataSource (as well as WrapperConnectionPoolDataSource)
+	   so that a mismatch between JDBC URL and driver does not lead to null Connection
+	   references and downstrem NullPointerExceptions. [Thanks to Richard Maher for
+	   calling attention to this issue.]
+	-- Undid some unnecessary synchronization in WrapperConnectionPoolDataSource,
+	   which would cause deadlocks when the acquisition of a Connection from the 
+	   underlying driver froze for some Thread. (Other threads would then block
+	   trying to access the WrapperConnectionPoolDataSource.) [Thanks to Fabian 
+	   Gonzales for calling attention to this issue, and for providing a VM thread
+	   stack dump to help chase it down!]
+	-- modified BasicMultiPropertiesConfig to never attempt to read the resource "/",
+	   representing (in this case) System properties, as a stream, which under some
+	   classloaders caused ClassCastExceptions. [Thanks to Joost Schouten for calling
+	   attention to this issue.]
+	-- fixed initial check of the readOnly and typeMap properties of Connections,
+	   so that users don't see disconcerting Exceptions at debug log levels if their drivers don't
+	   support these parameters.
+	-- Added jar attributes Extension-Name, Specification-Vendor, Specification-Version,
+	   Implementation-Vendor-Id, Implementation-Vendor, and Implementation-Version
+           [per request of Pascal Grange].
+	-- Added some extra debugging information for peculiar situation where an IdentityTokenized
+	   is constructed and registered with C3P0Registry, but coalesces to a different Object. 
+	   This should never happen, as freshly constructed IdentityTokenizeds should have String
+	   versions of their identity hashcodes as tokens, and upon their registration, no copies 
+	   should yet have been created and registered. [Thanks to Jose Rodriguez for noting this 
+	   issue.]
+c3p0-0.9.0.2
+	-- added debug-level logging of connection tests and results
+	-- tightened up means by which the readOnly and typeMap properties of Connections are reset,
+	   so that users don't see disconcerting Exceptions at debug log levels if their drivers don't
+	   support these parameters.
+	-- fixed problem in BasicResourcePool that left the immediately-close-checked-out-connections option
+	   in the close() method ineffective, meaning Connections checked-out from a pool would
+ 	   leak unless clients remembered to check them into pool for destruction.
+        -- fixed bug wherein connection-testing on checking occured in a thread holding the pool's
+	   lock, leading to pool hangs or deadlocks if a connection hangs. [Thanks to Tobias Jenkner
+	   for calling attention to this problem!]
+c3p0-0.9.0.1
+        -- modify BasicPropertiesConfig to not throw class cast Exceptions in the rare case that
+	   a Properties object (presumably System properties rather than a file derived props Object)
+	   contains (via the Map/Hastable API) non-String keys or values. [Thanks to Prima Upot for
+	   calling attention to this issue.]
+	-- fixed a build issue where some classes required for code generation were not necessarily
+	   compiled prior to the code generation step, causing builds to fail. [Thanks to James
+	   Neville for calling attention to this issue.]
+c3p0-0.9.0
+	-- under some circumstances, the current list of ThreadPoolAsynchronousRunner's deadlock detector
+	   seems to hold onto a lot of memory... presumably this is when long pending task lists build up 
+	   for some reason... nevertheless, we now dereference the "current" list in between deadlocker detector
+	   invocations. The "last" list has to be retained between invocations, but the "current" list need
+	   not be. [Thanks to Venkatesh Seetharamaiah for calling attention to this
+	   issue, and for documenting the apparent source of object retention.]
+	-- modified log4j and jdk14logging MLog implementations to test for library classes upon
+	   classload (MLog class only tests for the presence of the mlog implementations, which
+	   doesn't notice if their dependant classes aren't there -- dependent classes must test
+	   for the presence of the libraries.)
+	-- Modified GooGooStatementCache to warn as INFO (and with a descriptive message in debug)
+	   of multple PreparedStatement preparation. [Thanks to mxnmatch on hibernate's forum for 
+	   calling attention to this issue.]
+	-- Modified BasicResourcePool to not warn on expected InterruptExceptions that occur if
+	   clients are waiting for a Connection when the pool is closed. [Thanks to Blunted on
+	   hibernate's forum for calling attention to this issue.]
+c3p0-0.9.0-pre6
+	-- Modified thread pool's strategy for dealing with deadlocks to prevent OutOfMemoryErrors
+	   from sudden spawning of multiple threads to clear backlogged tasks. [Thanks to Greg Whalin
+	   for calling attention to this problem.]
+	-- Fixed bugs in debug reporting of nested Exceptions/Warnings when converting or
+	   rethrowing SQLExceptions [thanks to an anonymous sourceforge poster for finding this]
+	-- modified BasicResourcePool to shorten the period during which the pool's lock
+	   is held during resource acquisition 
+	-- maxIdleTime is generally enforced every (maxIdleTime / 8) seconds. Added logic to ensure that 
+	   even for very long maxIdleTimes, the longest maxIdleTime will ever go unenforced is 15 minutes.
+	-- Very tentative initial support in build for JUnit tests
+	-- Fixed documentation typos and added sample config for Tomcat 5.5. [Thanks to David Newcomb 
+	   for finding the typos and to Carl F. Hall for the sample config!]
+	-- ProxyConnections were not properly resetting readOnly, holdability, catalog, and typeMap
+	   properties on Connection close. Fixed. [Thanks to Andy (bjorkmann?) for calling attention
+	   to this bug.]
+	-- Added a complicated workaround to issues that may occur when a Connection
+	   and its child statements simultaneously try to close. GooGooStatementCache's closeAll(Connection c)
+	   method now wait()s for all Statements to close prior to returning, avoiding such issues (Oracle deadlock,
+	   mysql NullPointerException), though dramatically complicating the code. Grrr. Theoretically, drivers should
+	   be robust to the simultaneous closing of Statements and Connections, but they're not, so
+	   my code gets to be more complicated. [Thanks to Juan Perez, Daniel Edberg, and Damien Evans for calling 
+	   attention to this issue.]
+c3p0-0.9.0-pre5
+	-- Proxy classes were overaggressively invalidating themselves in response to a signalled
+	   Connection error. This problem was very similar to the problem fixed in 0.8.5-pre9, except
+	   for rather than the PooledConnection too quickly closing its inner Connection, the proxies
+	   too quickly detached themselves from the pooled Connection, considering themselves broken,
+	   after a Connection error event. Users expect to be able to work with their Connections even
+	   after c3p0 has in its conservatism decided they no are no longer worthy of a place in the
+	   pool. [Thanks to "Geoff Fortytwo" for finding and helping to track down this issue.]
+	-- Fixed issue that prevented user-defined ConnectionTesters from being used.
+c3p0-0.9.0-pre4
+	-- Fixed NullPointerException on double-close of proxy Statements. [Thanks to Stephen Waud
+	   for finding this bug and tracking it down.]
+	-- Modified C3P0PooledConnectionPool to generate a more informative Exception (one that
+	   includes the stack trace of the initial problem) when a Connection test fails. [Thanks
+	   to novotny from the hibernate forums for calling attention to this issue.]
+`	-- PooledDataSources now set SQLState 08001 on SQLExceptions resulting from a determination
+	   by the pool that the database is down or unavailable. [Thanks to ml2709 on the hibernate
+	   forums for calling attention to this issue.]
+	-- Modified ThreadPoolAsynchronousRunner to enforce its max task time even on one-off
+	   "emergency threads" used to flush the pool of tasks after an apparent deadlock.
+	   Hopefully this will help to eliminate the OutOfMemoryErrors that users occasionally
+	   see if some Database operation starts to fail by blocking indefinitely, and is attempted
+	   many many times. As long as these operations are susceptable to interrupt(), this should
+	   resolve the problem. (If tasks block in a way that is immune to interrupt(), there is
+	   little c3p0 can do to recover the Threads and prevent the number of blocked Threads
+	   from growing until memory runs out.) [Thanks to Jean-Christophe Rousseau and Arup Vidyerthy 
+	   for calling attention to this issue.]
+	-- Wrapped System.getProperties() calls in try / catch blocks that  catch and log 
+	   the SecurityExceptions, and ignore System properties if access to them is forbidden. 
+	   c3p0.properties configuration should work fine in security-controlled apps, but you 
+	   won't be able to configure c3p0 using "java -Dc3p0.xxxx=yyy ..." Since most users use 
+	   c3p0.properties for configuration, I don't think this will be a major problem. In the 
+	   future, I may be able to work around the restriction by having ask only for c3p0-specific 
+	   System properties, rather than calling the unrestricted System.getProperties() method. 
+	   [Thanks to zambak on SourceForge for calling attention to this issue.]
+c3p0-0.9.0-pre3
+	-- Modified generated proxy classes to properly report the type of the closed Object
+	   if used after call to close().
+	-- Fixed a really embarrassing oversight, wherein cached PreparedStatements
+	   were being physically close()ed prior to their return to the cache! Tests
+	   on psql failed to reveal this, because psql-7.4 Statement.close() does nothing
+	   to invalidate the Statement! [Many thanks to tbayboy on hibernate's forum for
+	   discovering this issue, and to fassev, also on hibernate's forum, for correctly
+	   deducing the cause!]
+	-- Fixed an embarrassing oversight where operations of proxy Statements
+	   and ResultSets failed to mark transactions as potentially dirty, leading
+	   to the possibility that Connections with unresolved transactional work could
+	   be checked into the pool.
+	-- Modified c3p0 Statements to no longer permit calls to getConnection()
+	   after Statement close, and to null out the backreference to the
+	   parent Connection. [Thanks to Edward Bridges for pointing out the issue.]
+	-- NewPooledConnection threw a NullPointerException on close() of
+	   a ResultSet whose creating Statement had already been closed.
+	   Fixed. [Thanks to Edward Bridges for pointing out the problem.]
+	-- Modified PoolConfig to trim() read-in c3p0.properties to avoid
+	   NumberFormatExceptions if there is extra spacing in the file.
+	   [Thanks to johnchan for calling attention to this issue.]
+c3p0-0.9.0-pre2
+	-- Documentation updates and improvements
+	-- Integrated configurable wrapper logging library, so that
+	   c3p0 can log to log4j or jdk14logging.
+c3p0-0.9.0-pre1
+	-- First pass at documentation of JBoss integration.
+	-- Better one-per-JVM canonicalization (IdentityTokenized,
+	   IdentityTokenResolvable, C3P0Registry)
+	-- First implementation of MBean for JBoss compatability
+	-- Fixed JNDI ref-based DataSources
+c3p0-0.8.5.1
+        -- Fixed a really embarrassing bug that made Statement pooling worse than
+           useless where users have set maxStatements but not maxStatementsPerConnection.
+           maxStatementsPerConnection -- that is, 0 -- was being used in place of maxStatements
+           on Statement cache initialization. [Thanks to Gwendal Tanguy for not only,
+           calling attention to this issue, but tracking down the precise location of
+           the bug!]
+c3p0-0.8.5
+	-- Documentation updates and improvements
+	-- Fixed an issue where failures on reset() of a pooled Connection caused
+	   a Connection error to be signalled, without updating the status of the
+	   PooledConnection to indicate that it is broken. Thanks to Henry Le for
+	   calling attention to this issue.
+c3p0-0.8.5-pre9
+	-- Modified c3p0's behavior on detecting an apparently broken Connection.
+	   Previously c3p0 would very aggressively close any Connections that, after
+	   an Exception, failed to pass a Connection test. This sometimes surprised
+	   and confused users, when Conections they expected to remain open were 
+	   closed from underneath them. Under normal circumstances, a Connection
+	   which has failed its Connection test is broken anyway, so closing it
+	   does no harm. But under some circumstances, a Connection may fail its
+	   Connection test, but still be partly functional from the application's
+	   point of view. So, now c3p0 simply marks Connections that fail their
+	   Connection test following an Exception as broken, and excludes them from
+	   future re-entry into the pool, but leaves those Connections open for users 
+	   to continue working with them prior to their explicit close() of the
+	   Connection. [Thanks to Julian Legeny for calling attention to this issue.]
+	-- Modified c3p0's logging behavior to be a little bit less annoying. Rather than
+	   frequently printing duplicate stack traces to be sure root cause Exceptions are
+	   logged as well as converted, rethrown Exceptions, c3p0 now checks the Java 
+	   version, and uses the initCause() method to log the root cause in versions 1.4
+	   and above. Also, some Exceptions that were expected and thrown without information
+	   as to the cause now use the initCause() method to provide this in JDK 1.4 and
+	   above. [Thanks to Ruslan Gainutdinov, Anthiny Pereira, Carsten ????, and 
+	   Eric Hsieh for calling attention to these issues.]
+	-- Fixed bug whereby proxy wrappers were sometimes placed around null return values
+	   from native Objects, particularly Statement's getResultSets() method. [Thanks to 
+	   Ruslan Gainutdinov for calling attention to this issue.]
+        -- Fixed bug in NewProxyConnection whereby setTransactionIsolation was called even
+	   if the Connections' isolation level had not been changed from its default. [Thanks
+	   to Levent Aksu for reporting this bug.]
+c3p0-0.8.5-pre8
+	-- Added C3P0ProxyStatement and rawStatementOperations for accessing vendor-specific
+	   Statement API.
+	-- Fixed bug whereby recently added properties could not be configured properly
+	   via a PoolConfig Object passed to the DataSources factory method. [Thanks to
+	   Julian Legeny for finding this bug.]
+	-- Undid new behavior whereby c3p0 warns when it detects that a transaction
+	   has not been comitted or rolled-back prior to close and must be automatically
+	   rolled-back. Because c3p0 autorollsback conservatively, whenever it is possible
+	   that there may be any transactional resources held by the Connection being
+	   checked in, users who make read-only queries, then close without commit or
+	   rollback, saw the warning, and became annoyed. Warning when it's "right" is hard,
+	   and would involve paying attention to the substance of user-queries, as well
+	   as the transaction's isolation level. In the future, when c3p0 offers better
+	   control over logging, we'll make the warning optional. Note that this release
+	   only undoes the warning -- the new logic that detects potential transactional work
+	   and only rollsback when there has been some remains. [Thanks to David Graham for
+	   calling attention to this issue.]
+	-- Synchronized StatementCacheKey.find( ... ) method. Subclasses of StatementCacheKey
+           have always explicitly relied upon this method being synchronized in the parent class, 
+           but somehow it was not... [Thanks to Manfred Hutt for reporting this bug.]
+	-- Modified both PooledConnection implementations to reset the transaction
+	   isolation level on PooledConnection.reset()
+	-- Finalized PooledDataSource api for pool stats and resets.
+c3p0-0.8.5-pre7a
+	-- Fixed some bugs in new Proxy classes relating to close(), methods,
+	   especially NullPointerExceptions on duplicate close() operations.
+	-- Fixed some misleading documentation re: testConnectionOnCheckin and
+	   automaticTestTable vs. preferredTestQuery
+	-- Changed wording of warning message on purge of idle-test-failing
+	   resource to no longer suggest that some user action is required.
+	   [Thanks to Krishna Kuchibhotla for calling attention to this
+	   issue.]
+	-- Modified GooGooStatementCache so that its close() is synchronous,
+	   since when the Statement cache is closed, it's asynchronous
+	   runner has often been closed already underneath it. (Actually
+	   changed as of c3p0-0.8.5-pre7, but forgot to log it.)
+c3p0-0.8.5-pre7
+	-- Added flag (in both new and tradional reflective proxies) to
+	   prevent automatic rollbacks on Connection close()es immediately post-
+	   commit(), rollback(), or setAutoCommit().
+	-- DatabaseMetaData now properly returns proxy rather than naked 
+	   raw Connection. [Thanks to jhoeller on the hibernate forums]
+	-- In response to an apparent BEA WebLogic specific problem with 
+	   DriverManager.getConnection( ... ), DriverManagerDataSource now
+	   caches its driver and uses Driver.connect( ... ) rather than 
+	   DriverManager.getConnection() when acquiring Connections. Thanks to
+	   Lars Torunski for calling attention to this issue.
+	-- Fixed some missing synchroniztion that really should be provided for accessors
+	   and mutators of DataSource bean properties.
+	-- Added config parameter automaticTestTable, which takes the name of a table
+	   that c3p0 will create and use as the basis for Connection tests. It's easier
+	   to use than preferred test query, because you don't have to ensure the existance
+	   of any tables in the schema prior to using a Pooled data source.
+	-- Fixed idiotic oversight whereby Connections didn't get their transaction status
+	   resolved and reset on check-in...
+c3p0-0.8.5-pre6
+	-- Modified resource pools to only conditionally support async ResourcePoolEvent
+	   generation. Since c3p0 doesn't use ResourcePoolEvents, we can do without the
+	   superfluous CarefulRunnableQueue, and its associated Thread, which was serving
+	   as an event queue.
+	-- Fixed bug where asynchronous refurbishment (testing) of resources would be
+	   attempted even on check-in to a closed pool, whose async threads had terminated.
+	-- c3p0 now supports the following new configuraion parameters: preferredTestQuery,
+           testConnectionOnCheckin, checkoutTimeout, and maxStatementsPerConnection.
+	   preferredTestQuery should allow c3p0 to test Connections (whether on checkout,
+	   checkin, or after a specified idle time) much more efficiently than with
+	   it's default test [a call to getTables(...) on the Connection's DatabaseMetaData.].
+	   checkoutTimeout allows clients to break out of a getConnection() call after a 
+	   a specified time period, rather than waiting (potentially indefinitely) for
+	   a new Connection to be acquired from the database, or a checked-out Connection
+	   to be rechecked in. testConnectionOnCheckin should be self-explanatory.
+           maxStatementsPerConnections allows users to specify how many statements to cache
+	   on a per-connection base instead of, or in addition to, the global limit maxStatements.
+	-- Fixed various bugs relating to the referenceMaker and getReference() method of
+	   ComboPooledDataSource.
+	-- Added TestUtils class to util package (modified from an old C3P0TestUtils
+	   class now removed from the test directory), which now contains facilities
+	   for establishing the identity of the physical Connection beneath a proxy,
+	   so that tests can check to see when they are seeing the same physical
+	   Connections recycled. [Thanks to Julian Legeny for calling attention to this
+	   issue.]
+	-- Modified code generators so that inner objects of all NewProxy classes
+	   go to null on object close(). Closed proxies should be hopelessly broken.
+	-- Added missing parameter "usesTraditionalReflectiveProxies" 
+	   to ComboPooledDataSource
+	-- Fixed bug whereby CarefulRunnableQueue threads linger indefinitely, even
+	   though their close() method has been called. [Thanks to muirwa on the
+	   hibernate forums for calling attention to this issue.]
+c3p0-0.8.5-pre5
+	-- added utility class for Oracle users who wish to access vendor specific
+	   Connection API relating to BLOBs and CLOBs. [Thanks to Dave Smith for calling
+	   attention to this issue.]
+	-- modified ThreadPoolAsynchronousRunner to provide more information should an 
+	   "apparent deadlock" occur, and fixed that class' recovery strategy from such
+	   an event. [Thanks to Damien Evans for calling attention to this issue.]
+c3p0-0.8.5-pre4
+	-- added config parameter usesTraditionalReflectiveProxies, which defaults to false,
+	   and stitched in the 'new' codegenerated, non-reflective proxy implementation for
+	   the default case.
+c3p0-0.8.5-pre3
+	-- Added new config parameters breakAfterAcquireFailure, acquireRetryAttempts, and 
+	   acquireRetryDelay to ComboPoolBackedDataSource (forget to modify this class when 
+	   I added the params earlier.)
+	-- Added description of raw connection operations to docs.
+c3p0-0.8.5-pre2
+	-- Defined AuthMaskedProperties object, and used this to prevent usernames
+	   and passwords from being dumped to logs (a security issue). [Thanks to Zac Jacobson
+	   for calling attention to this issue.]
+	-- Made BasicResourcePool's refurbishment of checked-in resources
+	   asynchronous, and used this to add a test of the resource on check-in.
+	   But since the extra test may slow stuff down substantially, I've disabled it
+	   pending a testConnectionsOnCheckin config parameter.
+	-- Fixed NullPointerException in C3P0ImplUtils.findAuth(Object o) which
+	   occurred when the Object at issue had write-only properties. [Thanks to 
+	   mrfekson for calling attention to this issue.]
+	-- Added acquireRetryAttempts, acquireRetryDelay, and breakAfterAcquireFailure
+	   as configurable properties. Supporting breakAfterAcquireFailure == false
+	   required substantial changes to BasicResourcePool. [Thanks to Zac Jacobson
+	   for suggesting the behavior for breakAfterAcquireFailure == false.]
+	-- Added poolOwnerIdentityToken property to PoolBackedDataSource, 
+	   which becomes part of the state of C3P0PooledConectionPoolManager,
+	   to avoid possibility that multiple, identically configured pools 
+	   that users create separately and consider distinct would be closed
+	   if any one of them were closed. This parameter ensures that for
+	   each user-created DataSource, there will be a distinct 
+	   C3P0PooledConectionPoolManager. Multiple copies, as often arise
+	   via deserialization and/or dereferencing JNDI DataSources, will
+	   still share a single pool and configuration.
+	-- Added reset methods, hard and soft, to PooledDataSource and 
+	   its implementations. Also added some extra aggregate pool statistics
+	   methods. [Thanks to Travis Reeder for suggesting the hardReset() behavior.]
+	-- Fixed bug that PoolBackedDataSource always provided statistics for the
+	   default authentication pool, even when alternate usernames and passwords
+	   were provided.
+c3p0-0.8.5-pre1
+        -- Defined the C3P0ProxyConnection interface, and modified both reflective and unreflective
+	   proxy Connection code to implement it. The interface provides a method through which 
+	   advanced users can work with the raw, unproxied database Connection. This was motivated
+	   by some Oracle-specific API that could not be passed through the generic Connections
+	   returned by c3p0 pools. Thanks to Dave Smith for calling attention to this issue.
+	-- Fixed a NullPointerException that resulted when users called DriverManagerDataSource's
+	   getConnection( username, password ) with either username or password as null. Users
+	   will now see the SQLException provided by their database for bad authentication, which 
+	   should make the problem more obvious.
+c3p0-0.8.4.5
+	-- Modified all variants of StatementCacheKey, as well as C3P0PooledConnection (proxy Statements)
+	   to support jdbc3.0 API for autogenerated keys and ResultSet holdability. All jdbc 3.0 API
+	   is now supported.
+c3p0-0.8.4.2
+	-- Fixed bug in which proxy Statements and ResultSets returned their 
+	   naked, unproxied parents from their getConnection() / getStatement()
+	   methods. Thanks to Christian Josefsson for noticing this!
+	-- Added check to ensure that ResourcePools are not broken prior to
+	   posting asynchronous events. It'd be better to factor the (as yet
+	   unutilized async event stuff into a subclass to simplify 
+	   BasicResourcePool).
+	-- Fixed documentation typo that said default numHelperThreads was "false"... 
+	   now it's correctly set to "3".
+	-- Added extra System.err information to instrument the causes of broken
+	   resource pools.
+	-- finalize() method of BasicResourcePool checks to be sure user has not
+	   already closed the pool before closing, to avoid spurious multiple
+	   close warnings. Thanks to Gavin King.
+c3p0-0.8.4.1
+	-- Added (hopefully not too annoying) dump of DataSource configuration
+	   on pool initialization to assist user debugging.
+	-- Added methods to force destroy all resources used by a PooledDataSource,
+	   even if other DataSource instances are sharing those resources. This is
+	   intended primarily for applications that wish to discard the ClassLoader that
+	   loaded c3p0, regardless of whatever's currently going on.
+	-- Turned off annoying trace messages when a C3P0PooledConnection closes.
+	-- Fixed issue in statement cache whereby statements that fail to check-in
+	   properly (that throw an exception in "clearParameters") cause an exception
+	   in removeStatement, because removeStatement sees a statement that appears
+	   neither to be checked-out, nor in the deathmarch for checked-in statements.
+	   Resolved by re-adding truculent statement to the checkedOut set, and then
+	   destroying using removeStatement's codepath for removing and (force-)destroying
+	   checked-out statements. Thanks to Zach Scott for calling attention to
+	   this issue.
+c3p0-0.8.4
+	-- Updated and HTML-ized documentation, updated READMEs. 
+c3p0-0.8.4-test5
+	-- Includes, but does not yet use, cleaner, nonreflective
+	   reimplementation of C3P0PooledConnection and all of the
+	   JDBC proxy wrappers. (c3p0-0.8.4 will stick with the old,
+	   tested implementation, c3p0-0.8.5 will replace.)
+	-- Removed temporary debug wrapper around acquired Connections
+	   added in c3p0-0.8.4-test4-3.
+c3p0-0.8.4-test4-3
+	-- Fixed race condition in BasicResourcePool close(), whereby
+	   pooled resources were to be destroyed by an asynchronous thread
+	   that itself was shut down by the close() method, so the resource
+	   closures did not necessarily occur. Pooled resources are now
+	   destroyed synchronously in BasicResourcePool.close()
+	-- Fixed erroneous call to C3P0PooledConnection reset() even when
+	   the PooledConnection is known to be broken.
+	-- Added temporary debug wrapper that dumps a stack trace on physical
+	   Connection close() to help Adrian track down a stubborn issue.
+c3p0-0.8.4-test4
+	-- Made sure PooledConnection's that are known to be broken are not reset()
+	   when the ProxyConnection that noticed the break cleans itself up.
+	-- Fixed a problem in C3P0PooledConnection's ProxyConnectionInvocationHandler
+	   when Connection-invalidating exceptions occurred inside the factored-out
+	   doSilentClose() method rather than in the invoke() method. This could lead to
+	   broken PooledConnections not being noticed and expunged from the pool 
+	   prior to recheck-out.
+	-- Ensured stack-trace of any Connection-invalidating Exceptions are
+	   logged. (Previously only the messages were logged.)
+	-- Access to ProxyConnections is now synchronized to avoid a possible
+	   race condition where a Connection could close while another method
+	   was in progress.
+	-- Cleaned up C3P0PooledConnection a bit, got rid of no longer used
+	   reflective code, modified to use new SQL interface filter classes.
+	-- Reorganized bean generation stuff to sit under com.mchange.v2.codegen
+	-- Added com.mchange.v2.sql.filter package, and code generation stuff so
+	   that abstract filter classes that implement JDBC interfaces can be
+	   easily regenerated from current versions. (This is preparation for
+	   JDK 1.4.x build support.)
+c3p0-0.8.4-test3
+	-- Reorganized C3P0PooledConnection and C3P0PooledConnectionPool so that
+	   PooledConnections only fire connectionErrorOccurred events on errors that
+	   signal the Connection is invalid. (Previously we fired the event in all
+	   cases, then tested the validity of the connection in the event handler.)
+	-- Fixed a bug where SQLExceptions that do not signal invalid Connections
+	   (such as an exception on transaction commit under optimistic concurrency)
+	   caused Connections to be closed, but still returned to the pool, leaving
+	   the pool corrupt. [Thanks to Adrian Petru Dimulescu for discovering and
+	   reporting this subtle problem!]
+c3p0-0.8.4-test2
+	-- Backed off a binary incompatible change to DataSources API. Users
+	   interested in statistics about running DataSources will need to
+	   cast the result of DataSources.pooledDataSource() to a
+	   com.mchange.v2.c3p0.PooledDataSource object. Other users should
+	   be able to use DataSources without recompilinbg libraries that
+	   depend upon it.
+	-- Defined com.mchange.v2.c3p0.ComboPooledDataSource, a single, directly-instantiable
+	   JavaBean-style DataSource backed by a c3p0 pool, which makes integration
+	   with various app servers easier (e.g. works with Tomcat), and which may
+	   provide a more straightforward API for users than DataSources and PoolConfig.
+c3p0-0.8.4-test1
+        -- Switched various classes from using RoundRobinAsynchronousRunner 
+           to a new ThreadPoolAsynchronousRunner.         
+        -- Defined PoolingDataSource interface, which permits clients to access
+           statistics about the state of underlying pools, and made C3P0 pool-backed
+           DataSources implement that interface.
+        -- fixed a subtle bug with respect to ownership of shared resources in which
+           finalize() methods for discarded DataSources could cause helper threads and
+           other resources to be cleaned up prematurely, while they are still in use
+           by other DataSources.
+        -- major reorganization of code generation for DataSources and the
+           resulting objects.
+        -- reorganization of Serializable and Referenceable code of DataSources
+           for JNDI Binding
+        -- made PoolConfig defaults (which are set by system properties, a 
+           c3p0.properties file, or else hardcoded defaults) apply to DataSources
+           that are directly instantiated rather than created via the 
+           com.mchange.v2.c3p0.DataSources factory class.
+	-- organized scattered stuff into a self-contained, distributable build
+	   directly, and divided source and binary distributions
+        -- relicensed library; now available under LGPL.
+	-- THERE ARE SOME MAJOR REORGANIZATIONS IN THIS RELEASE. ANY HELP IN
+	   TESTING AND FEEDBACK WOULD BE APPRECIATED! THANKS!
+c3p0-0.8.3.1
+        -- take greater care to abort if a helper thread wakes from wait()
+           inside of a broken resource pool
+        -- log more debug information if a pool unexpectedly breaks
+c3p0-0.8.3
+	-- resolved a deadlock arising from ConnectionEventSupport objects
+           calling event listener methods while holding the ConnectionEventSupport's
+           lock. (Thanks to an anonymous Sourceforge bug submitter for tracking this
+           down.)
+        -- added two configuration parameters to modify how c3p0 deals with
+           potentially unresolved transactions on transaction close -- 
+           autoCommitOnClose and forceIgnoreUnresolvedTransactions. c3p0's
+           default behavior is as it always was (and as I think it nearly
+           always should be) -- by default we roll back any uncommitted work
+           on close. See PoolConfig class for complete documentation.
+        -- fixed stupid build error that made unzipping c3p0 archives messy
+           since c3p0-0.8.2. (SORRY!)
+c3p0-0.8.3-pre-travis
+        -- fixed problem with c3p0 trying endlessly to connect to a database
+           that is down or not present. c3p0 now tries thirty time, once per
+           second, then gives up. if there's demand, I'll make the delay and
+           number of attempts user configurable. Thanks to Alfonso da Silva
+           for calling attention to this problem.
+        -- modified RoundRobinAsynchronousRunner to die more gracefully if, 
+           somehow, one of its CarefulAsynchronousRunners somehow dies. Thanks to
+           Travis Reeder for calling attention to this issue.
+        -- modified ResourcePools to allow resources to be tested periodically
+           and asynchronously while they are idle in the pool.
+        -- added to a new configuration property to PoolConfig and related
+           classes, idleConnectionTestPeriod, which if set to a greater-than-zero
+           value will cause c3p0 to periodically test idle, pooled connection,
+           and remove them from the pool if they are broken.  Thanks to
+           Travis Reeder for calling attention to this issue.
+        -- removed SQLState 08S01 from the set of SQL states presumed to mean
+           all connections in the pool have become invalid. Apparently MySQL
+           uses this SQLState to indicate the timing out of idle, individual
+           connections.  Thanks to Travis Reeder for calling attention to this issue.
+        -- many (!) bug-fixes, small tweaks and improvements.
+        -- temporarily sprinkled some debugging output prefixed "c3p0-TRAVIS" to 
+           try to resolve some issues for Travis Reeder
+c3p0-0.8.2
+        -- Addressed a rare problem that ocurred when application servers or
+           other external code calls interrupt() on c3p0 helper threads. Thanks 
+	   to Travis Reeder for reporting this issue! The endless cascade of 
+	   ArrayIndexOutOfBoundsException that would occasionallY result has 
+	   been fixed, and c3p0 helper threads now ignore interupts,
+           which should almost completely prevent such interrupts() 
+           from leaving c3p0 pools in a corrupt state. [In the very, very
+	   unlikely event that multiple, well-timed interrupts() cause
+	   a pool to diagnose itself broken, c3p0 will start throwing clear 
+	   exceptions to indicate that -- there shouldn't be any deadlock or
+	   any subtle kind of corruption.]
+	-- Deprecated PoolBackedDataSourceFactory and DriverManagerDataSourceFactory
+	   in favor of the newer DataSources class.
+        -- Updated example code and documentation.
+	-- Moved constants from DataSources to PoolConfig where they belong!
+c3p0-0.8.2-pre10
+        -- Removed use of properties default mechanism in setting up JDBC
+           properties for DriverManagerDataSource, as some drivers use get()
+           rather than getProperties(), and the Properties.get() method ignores
+           defaults. [Thanks go to Michael Jakl <mj at int-x.org> for both finding
+           and fixing this problem!!!]
+        -- Fixed subtle problem whereby the introspective constructor of 
+           C3P0PooledConnectionPoolManager would inadvertently check out an
+           extra pooled connection, by treating the getPooledConnection() as
+           a simple property read and invoking it. [Thanks go to Michael Jakl 
+           <mj at int-x.org> for both finding and fixing this bug!!!]
+c3p0-0.8.2-pre9
+        -- Finished DataSources static factory for, um, DataSources.
+        -- Added PoolConfig class to encapsulate all configuration
+           information from factories
+	-- Set up PoolConfig to pay attention to (in order):
+             1) explicit, user-defined parameters
+             2) parameters defined as System properties
+             3) parameters defined in a Properties file resource, at
+                resource pasth /c3p0.properties in PoolConfig's classloader
+	     4) Hardcoded defaults from the C3P0Defaults class
+        -- Defined a new pool configuration property, "testConnectionOnCheckout".
+           If set to true (it defaults to false), each pooled Connection will be 
+           verified at checkout time, before being supplied to clients. THIS SLOWS
+           CONNECTION CHECKOUT DRAMATICALLY (by an order of magnitude on my machine),
+           but some folks may want it. The expensive, verified Connections are still
+           faster to acquire than brand new ones.
+        -- If a Connection experiences an SQLException with a defined SQL State of
+           08001, 08007, or 08S01, the entire database is considered to have been
+           shutdown, and the pool whose connection experienced the problem is reset 
+           -- i.e. all existing Connections are discarded, and new Connections are 
+           acquired for the pool. (Thanks! to Gavin King for suggesting the SQL State
+           approach to detecting disconnects.)
+        -- ConnectionTester interface and its default implementation were modified
+           to support reporting DATABASE_IS_INVALID (rather than simply testing a
+           disconnected, individual Connection).
+c3p0-0.8.2-pre8
+        -- Fixed some really stupid bugs from pre-7, in particular
+           the static factory methods of the DataSources class were
+           not declared static. Oops. 
+c3p0-0.8.2-pre7
+        -- Major reorganization of data source implementations. These
+           are now generated from XML templates, because it was becoming
+           difficult to keep multiple versions of multiple types of 
+           datasources in sync with one another
+        -- a new factory / utility class has been added, 
+           com.mchange.v2.c3p0.DataSources.
+           Soon the older factories (DriverManagerDataSourceFactory and 
+           PoolBackedDataSourceFactory) will be deprecated in favor of this.
+        -- Began adding support for wrapper DataSources that lazily bind to 
+           an unpooled DataSource specified via JNDI. (Current wrappers require
+           an actual instance, not a mere reference in order to construct a
+           PoolBacked or ConnectionPoolDataSource).
+c3p0-0.8.2-pre6
+	-- Fixed bug that led to ConcurrentModificationException
+	   when Connection.close() iterates through and closes
+	   its unclosed Statements.
+	-- Fixed bug that caused uncached Statements to sometimes
+	   be closed twice, as they were not properly removed from
+	   the set of Connection-associated statements on user-
+           initiated Statement.close(), and would thus be closed 
+           again on Connection.close().
+	-- Reorganized classes to clean up top-level com.mchange.v2.c3p0
+	   package. The gory innards of c3p0 now live in an impl package.
+	-- Reorganized DataSources so that there are "Base" versions suitable
+	   for DBMS-specific subclassing.
+c3p0-0.8.2-pre5
+        -- DataSources looked up by JNDI now "coalesce" -- i.e., if
+           two separate calls look-up the same JNDI bound DataSource,
+           they will both see the same DataSource instance in the local
+           VM. This is especially important for PoolBackedDataSources, as they
+           own "helper threads" that help to manage the pool. Now each
+           logical DataSource shares the same pool of helper threads,
+           regardless of whether it is looked up once and cached, or
+	   looked up multiple times and at multiple places by an application. 
+        -- Number of helper-threads managing a PoolBackedDataSource is now
+           user-configurable
+c3p0-0.8.2-pre4
+        -- Fixed BAD memory leak -- cachedStatements were being retained
+           on close. This brings a performance improvement to
+	   the StatementCache as well.
+	-- Set-up StatementCacheKey to coalesce, such that its equals
+	   method can look like {return (this == o);}. Unfortunately, I
+	   did not see the kind of performance increase I'd anticipated
+	   from this... in fact, whatever performance changes I did see were
+	   pretty negligable. Performed some other, mostly useless, optimizations
+	   to the process of acquiring StatementCacheKeys. (I can't decide...
+           I've got 3 usuable, broadly similar versions of StatementCacheKey now.)
+	-- Fixed bugs having to do with when to create and the daemon status
+	   of Timers
+	-- Appropriately synchronized access to PoolBackedDataSource. (This
+	   should permit me to pull back on synchronization / marking of
+	   volatile in some dependent classes, as access to these is now 
+           controlled via PoolBackedDataSource.)
+	-- Made instantion of C3P0PooledConnectionPoolManager 
+           by WrapperPoolBackedDataSource "lazy", so that Timer / Async task
+	   threads are only started up when a Connection is actually requested 
+	   from a DataSource. This is useful especially for the instance residing
+	   in the JNDI server VM, whose purpose is likely only to serve as an
+	   instance to be looked up over the network. There's no reason why
+	   it should keep several stalled Threads open.
+	-- Fixed DefaultConnectionTester, whose logic was bass-ackwards.
+c3p0-0.8.2-pre3
+	-- Factory method for StatementCacheKeys now "coalesce" equivalent 
+	   instances to conserve memory and to enable a fast (this == o) test 
+	   to usually work in equals methods. [Value test remains for when 
+	   identity test fails... maybe later I'll be more comfortable with a 
+	   1 instance per value guarantee, and remove the rest of the test. 
+	   As the constructor is private, the factory method coalesces, and the 
+	   class is not Serializable, an identity test should suffice. But 
+	   I'm paranoid.]
+
+	-- All asynchronous tasks are now distributed to 3 helper threads per 
+	   DataSource rather than queuing on just one. (Culing of expired 
+	   Connections is still managed by a single java.util.Timer... I don't 
+	   think this will be a problem.)
+
+c3p0-0.8.2-pre2
+	-- Total rewite of Statement cache!
+
diff --git a/src/dist-static/LICENSE b/src/dist-static/LICENSE
new file mode 100644
index 0000000..b1e3f5a
--- /dev/null
+++ b/src/dist-static/LICENSE
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+

+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+

+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+

+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+

+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+

+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+

+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+

+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+

+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+

+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/src/dist-static/README b/src/dist-static/README
new file mode 100644
index 0000000..2a71093
--- /dev/null
+++ b/src/dist-static/README
@@ -0,0 +1,20 @@
+ _____________________________________
+   .................................
+   ..(c3p0).........................
+   ............a.........fresh......
+   ....coat.......of........stucco..
+   .........over.......that.....old.
+   ...jdbc........driver............
+   .................................
+ -------------------------------------
+      versions 0.8.4 and above
+           23-Dec-2003
+
+-> Feedback to Steve Waldman <swaldman at mchange.com> <-
+-> Please! :)                                       <-
+
+Hi. c3p0's docs have been HTML-ized, and moved to the "doc"
+directory of this distribution. Please check that out.
+
+Sorry all you plain text fans!
+
diff --git a/src/dist-static/TODO b/src/dist-static/TODO
new file mode 100644
index 0000000..783f84d
--- /dev/null
+++ b/src/dist-static/TODO
@@ -0,0 +1,28 @@
+full status dump operations from JMX beans (two methods, one std err onr to logger at INFO level?) 
+
+Statement cache statistics exposed via JMX.
+Interface to Statement-caching statistics
+
+make asynchronous checkins optional, let the default depend on the value of testConnectionOnCheckin.
+
+A shortcut in acquire tasks that bails before attempting an acquire if the acquisition
+is no longer necessary.
+
+Make visible rootCause exceptions when Connection test fails. (Looks implemented, but I don't see them. Debug.)
+
+Less confusing hibernate ConnectionProvider.
+
+JUnit testing regime.
+
+some validation stuff in C3P0PooledConnectionPoolManager...
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/dist-static/examples/JndiBindDataSource.java b/src/dist-static/examples/JndiBindDataSource.java
new file mode 100644
index 0000000..0b2b95a
--- /dev/null
+++ b/src/dist-static/examples/JndiBindDataSource.java
@@ -0,0 +1,80 @@
+import java.sql.*;
+import javax.naming.*;
+import javax.sql.DataSource;
+import com.mchange.v2.c3p0.DataSources;
+
+
+/**
+ *  This example shows how to acquire a c3p0 DataSource and
+ *  bind it to a JNDI name service.
+ */
+public final class JndiBindDataSource
+{
+    // be sure to load your database driver class, either via 
+    // Class.forName() [as shown below] or externally (e.g. by
+    // using -Djdbc.drivers when starting your JVM).
+    static
+    {
+	try 
+	    { Class.forName( "org.postgresql.Driver" ); }
+	catch (Exception e) 
+	    { e.printStackTrace(); }
+    }
+
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		// let a command line arg specify the name we will
+		// bind our DataSource to.
+		String jndiName = argv[0];
+
+  		// acquire the DataSource using default pool params... 
+  		// this is the only c3p0 specific code here
+		DataSource unpooled = DataSources.unpooledDataSource("jdbc:postgresql://localhost/test",
+								     "swaldman",
+								     "test");
+		DataSource pooled = DataSources.pooledDataSource( unpooled );
+
+		// Create an InitialContext, and bind the DataSource to it in 
+		// the usual way.
+		//
+		// We are using the no-arg version of InitialContext's constructor,
+		// therefore, the jndi environment must be first set via a jndi.properties
+		// file, System properties, or by some other means.
+		InitialContext ctx = new InitialContext();
+		ctx.rebind( jndiName, pooled );
+		System.out.println("DataSource bound to nameservice under the name \"" +
+				   jndiName + '\"');
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    static void attemptClose(ResultSet o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    static void attemptClose(Statement o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    static void attemptClose(Connection o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    private JndiBindDataSource()
+    {}
+}
diff --git a/src/dist-static/examples/UseJndiDataSource.java b/src/dist-static/examples/UseJndiDataSource.java
new file mode 100644
index 0000000..f63d19a
--- /dev/null
+++ b/src/dist-static/examples/UseJndiDataSource.java
@@ -0,0 +1,86 @@
+import java.sql.*;
+import javax.naming.*;
+import javax.sql.DataSource;
+
+/**
+ *  This example shows how to programmatically get and directly use
+ *  an unpooled DataSource
+ */
+public final class UseJndiDataSource
+{
+
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		// let a command line arg specify the name we will
+		// lookup our DataSource.
+		String jndiName = argv[0];
+
+		// Create an InitialContext, and lookup the DataSource in 
+		// the usual way.
+		//
+		// We are using the no-arg version of InitialContext's constructor,
+		// therefore, the jndi environment must be first set via a jndi.properties
+		// file, System properties, or by some other means.
+		InitialContext ctx = new InitialContext();
+
+		// acquire the DataSource... this is the only c3p0 specific code here
+		DataSource ds = (DataSource) ctx.lookup( jndiName );
+
+		// get hold of a Connection an do stuff, in the usual way
+		Connection con  = null;
+		Statement  stmt = null;
+		ResultSet  rs   = null;
+		try
+		    {
+			con = ds.getConnection();
+			stmt = con.createStatement();
+			rs = stmt.executeQuery("SELECT * FROM foo");
+			while (rs.next())
+			    System.out.println( rs.getString(1) );
+		    }
+		finally
+		    {
+			// i try to be neurotic about ResourceManagement,
+			// explicitly closing each resource
+			// but if you are in the habit of only closing
+			// parent resources (e.g. the Connection) and
+			// letting them close their children, all
+			// c3p0 DataSources will properly deal.
+			attemptClose(rs);
+			attemptClose(stmt);
+			attemptClose(con);
+		    }
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    static void attemptClose(ResultSet o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    static void attemptClose(Statement o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    static void attemptClose(Connection o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    private UseJndiDataSource()
+    {}
+}
diff --git a/src/dist-static/examples/UsePoolBackedDataSource.java b/src/dist-static/examples/UsePoolBackedDataSource.java
new file mode 100644
index 0000000..3eb6531
--- /dev/null
+++ b/src/dist-static/examples/UsePoolBackedDataSource.java
@@ -0,0 +1,83 @@
+import java.sql.*;
+import javax.sql.DataSource;
+import com.mchange.v2.c3p0.DataSources;
+
+
+/**
+ *  This example shows how to programmatically get and directly use
+ *  an pool-backed DataSource
+ */
+public final class UsePoolBackedDataSource
+{
+
+    public static void main(String[] argv)
+    {
+	try
+	    {
+		// Note: your JDBC driver must be loaded [via Class.forName( ... ) or -Djdbc.properties]
+		// prior to acquiring your DataSource!
+
+		// Acquire the DataSource... this is the only c3p0 specific code here
+		DataSource unpooled = DataSources.unpooledDataSource("jdbc:postgresql://localhost/test",
+								     "swaldman",
+								     "test");
+		DataSource pooled = DataSources.pooledDataSource( unpooled );
+
+
+
+		// get hold of a Connection an do stuff, in the usual way
+		Connection con  = null;
+		Statement  stmt = null;
+		ResultSet  rs   = null;
+		try
+		    {
+			con = pooled.getConnection();
+			stmt = con.createStatement();
+			rs = stmt.executeQuery("SELECT * FROM foo");
+			while (rs.next())
+			    System.out.println( rs.getString(1) );
+		    }
+		finally
+		    {
+			//i try to be neurotic about ResourceManagement,
+			//explicitly closing each resource
+			//but if you are in the habit of only closing
+			//parent resources (e.g. the Connection) and
+			//letting them close their children, all
+			//c3p0 DataSources will properly deal.
+			attemptClose(rs);
+			attemptClose(stmt);
+			attemptClose(con);
+		    }
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    static void attemptClose(ResultSet o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    static void attemptClose(Statement o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    static void attemptClose(Connection o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    private UsePoolBackedDataSource()
+    {}
+}
diff --git a/src/dist-static/examples/UseUnpooledDataSource.java b/src/dist-static/examples/UseUnpooledDataSource.java
new file mode 100644
index 0000000..72b9637
--- /dev/null
+++ b/src/dist-static/examples/UseUnpooledDataSource.java
@@ -0,0 +1,81 @@
+import java.sql.*;
+import javax.sql.DataSource;
+import com.mchange.v2.c3p0.DataSources;
+
+
+/**
+ *  This example shows how to programmatically get and directly use
+ *  an unpooled DataSource
+ */
+public final class UseUnpooledDataSource
+{
+
+    public static void main(String[] argv)
+    {
+	try
+	    {
+
+		// Note: your JDBC driver must be loaded [via Class.forName( ... ) or -Djdbc.properties]
+		// prior to acquiring your DataSource!
+
+		// Acquire the DataSource... this is the only c3p0 specific code here
+		DataSource ds = DataSources.unpooledDataSource("jdbc:postgresql://localhost/test",
+							       "swaldman",
+							       "test");
+
+		// get hold of a Connection an do stuff, in the usual way
+		Connection con  = null;
+		Statement  stmt = null;
+		ResultSet  rs   = null;
+		try
+		    {
+			con = ds.getConnection();
+			stmt = con.createStatement();
+			rs = stmt.executeQuery("SELECT * FROM foo");
+			while (rs.next())
+			    System.out.println( rs.getString(1) );
+		    }
+		finally
+		    {
+			//i try to be neurotic about ResourceManagement,
+			//explicitly closing each resource
+			//but if you are in the habit of only closing
+			//parent resources (e.g. the Connection) and
+			//letting them close their children, all
+			//c3p0 DataSources will properly deal.
+			attemptClose(rs);
+			attemptClose(stmt);
+			attemptClose(con);
+		    }
+	    }
+	catch (Exception e)
+	    { e.printStackTrace(); }
+    }
+
+    static void attemptClose(ResultSet o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    static void attemptClose(Statement o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    static void attemptClose(Connection o)
+    {
+	try
+	    { if (o != null) o.close();}
+	catch (Exception e)
+	    { e.printStackTrace();}
+    }
+
+    private UseUnpooledDataSource()
+    {}
+}
diff --git a/src/dist-static/examples/c3p0-service.xml b/src/dist-static/examples/c3p0-service.xml
new file mode 100644
index 0000000..826a43f
--- /dev/null
+++ b/src/dist-static/examples/c3p0-service.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE server>
+
+<server>
+
+   <mbean code="com.mchange.v2.c3p0.mbean.C3P0PooledDataSource"
+          name="jboss:service=C3P0PooledDataSource">
+     
+      <attribute name="JndiName">java:PooledDS</attribute>
+      <attribute name="JdbcUrl">jdbc:postgresql://localhost/c3p0-test</attribute>
+      <attribute name="DriverClass">org.postgresql.Driver</attribute>
+      <attribute name="User">swaldman</attribute>
+      <attribute name="Password">test</attribute>
+
+      <!-- Uncomment and set any of the optional parameters below -->
+      <!-- See c3p0's docs for more info.                         -->
+
+      <!-- <attribute name="AcquireIncrement">3</attribute>                     -->
+      <!-- <attribute name="AcquireRetryAttempts">30</attribute>                -->
+      <!-- <attribute name="AcquireRetryDelay">1000</attribute>                 -->
+      <!-- <attribute name="AutoCommitOnClose">false</attribute>                -->
+      <!-- <attribute name="AutomaticTestTable"></attribute>                    -->
+      <!-- <attribute name="BreakAfterAcquireFailure">false</attribute>         -->
+      <!-- <attribute name="CheckoutTimeout">0</attribute>                      -->
+      <!-- <attribute name="ConnectionTesterClassName">0</attribute>            -->
+      <!-- <attribute name="Description">A pooled c3p0 DataSource</attribute>   -->
+      <!-- <attribute name="FactoryClassLocation"></attribute>                  -->
+      <!-- <attribute name="ForceIgnoreUnresolvedTransactions">true</attribute> -->
+      <!-- <attribute name="IdleConnectionTestPeriod">-1</attribute>            -->
+      <!-- <attribute name="InitialPoolSize">3</attribute>                      -->
+      <!-- <attribute name="MaxIdleTime">0</attribute>                          -->
+      <!-- <attribute name="MaxPoolSize">15</attribute>                         -->
+      <!-- <attribute name="MaxStatements">0</attribute>                        -->
+      <!-- <attribute name="MaxStatementsPerConnection">0</attribute>           -->
+      <!-- <attribute name="MinPoolSize">0</attribute>                          -->
+      <!-- <attribute name="NumHelperThreads">3</attribute>                     -->
+      <!-- <attribute name="PreferredTestQuery"></attribute>                    -->
+      <!-- <attribute name="TestConnectionOnCheckin">false</attribute>          -->
+      <!-- <attribute name="TestConnectionOnCheckout">false</attribute>         -->
+      <!-- <attribute name="UsesTraditionalReflectiveProxies">false</attribute> -->
+
+
+      <depends>jboss:service=Naming</depends>
+   </mbean>
+
+</server>
+
+   
diff --git a/src/doc/arrow_sm.png b/src/doc/arrow_sm.png
new file mode 100644
index 0000000..9c97fdc
Binary files /dev/null and b/src/doc/arrow_sm.png differ
diff --git a/src/doc/index.html b/src/doc/index.html
new file mode 100644
index 0000000..3fbc297
--- /dev/null
+++ b/src/doc/index.html
@@ -0,0 +1,2474 @@
+<html>
+  <head>
+    <title>c3p0-v at c3p0.version@ - JDBC3 Connection and Statement Pooling - Documentation</title>
+    <script language="text/javascript">
+      function toggleDisplay( id1, id2 )
+      {
+        if (document.getElementById( id1 ).style.display == 'block')
+        {
+           document.getElementById( id1 ).style.display = 'none';
+           document.getElementById( id2 ).style.display = 'block';
+        }
+        else
+        {
+           document.getElementById( id2 ).style.display = 'none';
+           document.getElementById( id1 ).style.display = 'block';
+        }
+        return false;
+      }
+    </script>
+    <style type="text/css">
+      a.cfg_param {
+         font-family: monospace;
+      }
+      body {
+         font-family: optima, helvetica, arial, sans-serif;
+      }
+      dl.properties {
+         padding: 0em 3em 0em 3em;
+      }
+      dl.properties dd {
+         padding-bottom: 0.5em;
+         font-size: smaller;
+      }
+      dl.properties dt {
+         padding-top: 1em;
+         border-top-width: 1;
+         border-top-style: dashed;
+         border-top-color: black;
+         font-family: monospace;
+	 color: red;
+      }
+      dl.properties div.default {
+         padding-top: 0.5em;
+         text-decoration: underline;
+         padding-bottom: 0.5em;
+      }
+      dl.properties div.per-user {
+      	 text-align: center;
+         margin-top: 0.5em;
+	 font-weight: bold;
+	 border-width: 1;
+         border-style: solid; 
+         border-color: black;
+	 background-color: #FFCCCC;
+      }
+      dl.log_properties {
+         border-width: 2;
+         border-style: solid;
+         border-color: black;
+         padding: 0.5em 0.5em 0.5em 0.5em;
+         font-size: smaller;
+	 background-color: #DDDDDD;
+      }
+      dl.log_properties dd {
+         padding-top: 0.5em;
+         padding-bottom: 0.5em;
+         /* font-size: smaller; */
+      }
+      dl.log_properties dt {
+         padding-top: 1em;
+         font-weight: bold;
+         font-family: monospace;
+	 color: black;
+      }
+      div.boxed {
+        border-width: 1;
+        border-style: solid; 
+        border-color: black; 
+        padding: 1em; 
+        font-size: smaller;
+        background-color: #CCFFCC;
+        margin-top: 1em;
+        margin-bottom: 1em;
+      }
+      div.boxed h4 {
+        margin-top: 0;
+        padding-top: 0;
+        font-weight: bold;
+        text-decoration: underline;
+      }
+      div.deprecated {
+         display: none;
+         padding-top: 0;
+         padding-left: 1em;
+         padding-right: 1em;
+         padding-bottom: 1em;
+         background-color: #FFAAAA;
+         border: 2px solid red;
+      }
+      div.deprecated h5 {
+         font-weight: bold;
+         color: #CC0000;
+         margin-top: 0em;
+         padding-top: 1em;
+      }
+      div.example {
+         white-space: pre;
+         font-family: monospace;
+	 font-size: smaller;
+	 background-color: #CCCCFF;
+         margin: 3em 3em 3em 3em;
+         padding: 1em 0em 1em 2em;
+         border-top-width: 2; 
+         border-top-color: black; 
+         border-top-style: solid; 
+         border-bottom-width: 2; 
+         border-bottom-color: black; 
+         border-bottom-style: solid; 
+      /*
+         doesn't show under Firefox
+         border-top: 2 solid black;
+         border-bottom: 2 solid black;
+      */
+      }
+      div.example_properties {
+         white-space: pre;
+         font-family: monospace;
+	 font-size: smaller;
+	 background-color: #CCCCFF;
+         margin: 3em 3em 3em 3em;
+         padding: 1em 0em 1em 0em;
+         border-top-width: 2; 
+         border-top-color: black; 
+         border-top-style: solid; 
+         border-bottom-width: 2; 
+         border-bottom-color: black; 
+         border-bottom-style: solid; 
+      }
+      div.example_properties strong {
+         font-weight: bold;
+	 color: red;
+      }
+      div.hibernate_props {
+         text-align: center;
+      }
+      div.other_properties_desc {
+         padding-top: 2px;
+         padding-left: 2em;
+         padding-right: 2em;
+         padding-bottom: 5px;
+         margin-bottom: 2em;
+         text-align: justify;
+         background: #ddf;
+      }
+      div.sectiontext {
+         margin-left: 2em;
+         margin-right: 2em;
+      }
+      div.subtitle {
+	 font-size: 12pt;
+	 background-color: #FFFFFF;
+      }
+      div.top_boilerplate {
+         margin-left: 8em;
+         margin-right: 8em;
+      }
+
+      h1 {
+         text-align: center;
+	 background-color: #FFCCAA;
+      }
+      h2 {
+	 background-color: #FFFFAA;
+         margin-top: 2em;
+         border-bottom-width: 2; 
+         border-bottom-color: black; 
+         border-bottom-style: solid; 
+      }
+      h3 {
+         margin-right: -2em;
+      }
+      img {
+         border: 0;
+      }
+      ol.contents ol {
+         list-style-type: lower-roman;
+      }
+      ol.contents ol li {
+	 font-size: smaller;
+      }
+      ol.contents > ul.box {
+         list-style: circle;
+      }
+      ol.precedence {
+        width: 75%; 
+        border-width: 2; 
+        border-color: black; 
+        border-style: solid; 
+        padding-left: 3em; 
+        padding-top: 1em; 
+        padding-bottom: 1em; 
+        padding-right: 1em; 
+        text-align: left;
+      }
+
+      ol.precedence > li {
+	 font-size: smaller;
+         margin-top: 0.5em;
+      }
+      span.hibparam_comment {
+         font-family: optima, helvetica, arial, sans-serif;
+         font-style: italic;
+         text-decoration: underline;
+      }
+      span.toplink {
+         font-size: 12;
+         font-style: plain;
+         float: right;
+      }
+      table.beanPropSummaryTable {
+         width: 100%;
+         border: none;
+      }
+      table.beanPropSummaryTable tr td {
+         width: 33%;
+         border: none;
+         background: #ddf;
+         font-family: monospace;
+         line-height: 1.5;
+         padding: 8px;
+      }
+
+      table.beanPropSummaryTable tr td  a {
+         color: black;
+      }
+
+      table.hibernate_props {
+         border-width: 2;
+         border-color: black; 
+         border-style: solid; 
+	 font-size: smaller;
+         margin-left: auto;
+         margin-right: auto;
+      }
+      table.hibernate_props th {
+	 background-color: #FFCCAA;
+      }
+      table.hibernate_props td {
+         font-family: monospace;
+      }
+      ul.other_props_list {
+         list-style: none;
+         font-family: monospace;
+         color: #A00;
+      }
+      ul.pointerlist {
+         list-style: square;
+      }
+
+    </style>
+  </head>
+  <body>
+    <h1>
+      <div>c3p0 - JDBC3 Connection and Statement Pooling</div>
+      <div class="subtitle">version @c3p0.version@</div>
+    </h1>
+    <div class="top_boilerplate">
+      <p>by Steve Waldman <<a href="mailto:swaldman at mchange.com">swaldman at mchange.com</a>></p>
+      <p>© 2006 Machinery For Change, Inc.</p>
+      <p><i>
+	  This software is made available for use, modification, and redistribution,
+	  under the terms of the <a href="../LICENSE">Lesser GNU Public License (LGPL)</a>, which you should
+	  have received with this distribution.
+      </i></p>
+      <ul class="pointerlist"> 
+	<li>API docs for c3p0 are <a href="apidocs/index.html">here</a>.</li> 
+	<li>Download the latest version from <a href="http://sourceforge.net/projects/c3p0/">c3p0's site on SourceForge</a></li>
+	<li>Looking for the definition of <a href="#configuration_properties">configuration properties</a>?</li>
+	<li>Looking for advice in <a href="#hibernate-specific">using c3p0 with hibernate</a>?</li>
+      </ul>
+    </div>
+    <hr/>
+    <h2><a name="contents">Contents</a></h2>
+    <ol class="contents">
+      <li><a href="#contents">Contents</a></li>
+      <li><a href="#quickstart">Quickstart</a></li>
+      <li><a href="#what_is">What is c3p0?</a></li>
+      <li><a href="#prerequisites">Prerequisites</a></li>
+      <li><a href="#installation">Installation</a></li>
+      <li>
+	<a href="#using_c3p0">Using c3p0</a>
+	<ol class="contents">
+	  <li><a href="#using_combopooleddatasource">Using ComboPooledDataSource</a></li>
+	  <li><a href="#using_datasources_factory">Using the DataSouces factory class</a></li>
+	  <ul class="box">
+	  	<li><a href="#forceOverrideBox">Box: Overriding authentication information (from non-c3p0 DataSources)</a></li>
+	  </ul>
+	  <li><a href="#querying">Querying Pool Status</a></li>
+	  <ul class="box">
+	  	<li><a href="#using_c3p0_registry_box">Box: Using C3P0Registry to find a reference to a DataSource</a></li>
+	  </ul>
+	  <li><a href="#cleaning">Cleaning Up Pool Resources</a></li>
+	  <li><a href="#build_your_own">Advanced: Building Your Own PoolBackedDataSource</a></li>
+	  <li><a href="#raw_connection_ops">Advanced: Raw Connection and Statement Operations</a></li>
+	</ol>
+      </li>
+      <li>
+	<a href="#configuration">Configuration</a>
+	<ol class="contents">
+	  <li><a href="#configuration_introduction">Introduction</a></li>
+	  <li><a href="#basic_pool_configuration">Basic Pool Configuration</a></li>
+	  <li><a href="#managing_pool_size">Managing Pool Size and Connection Age</a></li>
+	  <li><a href="#configuring_connection_testing">Configuring Connection Testing</a></li>
+	  <li><a href="#configuring_statement_pooling">Configuring Statement Pooling</a></li>
+	  <li><a href="#configuring_recovery">Configuring Recovery From Database Outages</a></li>
+	  <li><a href="#connection_customizers">Managing Connection Lifecycles with Connection Customizers</a></li>
+	  <li><a href="#configuring_unresolved">Configuring Unresolved Transaction Handling</a></li>
+	  <li><a href="#configuring_to_debug_and_workaround_broken_clients">Configuring to Debug and Workaround Broken Client Applications</a></li>
+	  <li><a href="#other_ds_configuration">Other DataSource Configuration</a></li>
+	  <li><a href="#jmx_configuration_and_management">Configuring and Managing c3p0 via JMX</a></li>
+	  <li><a href="#configuring_logging">Configuring Logging</a></li>
+	</ol>
+      </li>
+      <li><a href="#known_shortcomings">Performance</a></li>
+      <li><a href="#performance">Known shortcomings</a></li>
+      <li><a href="#feedback_and_support">Feedback and support</a></li>
+      <li><a href="#configuration_properties">Appendix A: Configuration Properties</a></li>
+      <li><a href="#configuration_files">Appendix B: Configuation Files, etc.</a></li>
+	<ol class="contents">
+	  <li><a href="#c3p0_properties">Overriding c3p0 defaults with a c3p0.properties file</a></li>
+	  <li><a href="#system_properties">Overriding c3p0 defaults with a System properties</a></li>
+	  <li><a href="#c3p0-config.xml">Named and Per-User configuration: Overriding c3p0 defaults via c3p0-config.xml</a></li>
+	  <li><a href="#configuration_precedence">Precedence of Configuration Settings</a></li>
+	</ol>
+      <li><a href="#hibernate-specific">Appendix C: Hibernate-specific notes</a></li>
+      <li><a href="#tomcat-specific">Appendix D: Configuring c3p0 pooled DataSources for Apache Tomcat</a></li>
+      <li><a href="#jboss-specific">Appendix E: JBoss-specific notes</a></li>
+      <li><a href="#oracle-specific">Appendix F: Oracle-specific API: createTemporaryBLOB() and createTemporaryCLOB()</a></li>
+    </ol>
+      (See also the API Documentation <a href="apidocs/index.html">here</a>)
+    <hr/>
+    <h2><a name="quickstart">Quickstart</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	c3p0 was designed to be butt-simple to use. Just put
+	the jar file [<tt>lib/c3p0- at c3p0.version@.jar</tt>] in your application's
+	effective <tt>CLASSPATH</tt>, and make a DataSource like this:
+      </p>
+      <div class="example">
+import com.mchange.v2.c3p0.*;
+	
+...
+	
+ComboPooledDataSource cpds = new ComboPooledDataSource();
+cpds.setDriverClass( "org.postgresql.Driver" ); //loads the jdbc driver            
+cpds.setJdbcUrl( "jdbc:postgresql://localhost/testdb" );
+cpds.setUser("dbuser");                                  
+cpds.setPassword("dbpassword");                                  
+      </div>
+      <p>
+	<b>[Optional]</b> If you want to turn on PreparedStatement pooling, you must also set <tt>maxStatements</tt> 
+	and/or <tt>maxStatementsPerConnection</tt> (both default to 0):
+      </p>
+      <div class="example">
+cpds.setMaxStatements( 180 );                                  
+      </div>
+      <p>
+	Do whatever you want with your DataSource, which will be backed
+	by a Connection pool set up with default parameters. You
+	can bind the DataSource to a JNDI name service, or use it
+	directly, as you prefer.
+      </p>
+      <p>
+	When you are done, you can clean up the DataSource you've created
+	like this:
+      </p>
+      <div class="example">
+DataSources.destroy( cpds );
+      </div>
+      <p>
+	That's it! The rest is detail.
+      </p>
+    </div>
+    <h2><a name="basics">What is c3p0?</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	c3p0 is an easy-to-use library for making traditional JDBC drivers 
+	"enterprise-ready" by augmenting them with functionality defined by 
+	the jdbc3 spec and the optional extensions to jdbc2. In particular, 
+	c3p0 provides several useful services:
+      </p>
+      <ul>
+	<li>
+	  Classes which adapt traditional DriverManager-based JDBC
+	  drivers to the new <tt>javax.sql.DataSource</tt> scheme for acquiring
+	  database Connections.
+	</li>
+	<li>
+	  Transparent pooling of Connection and PreparedStatements
+	  behind DataSources which can "wrap" around traditional
+	  drivers or arbitrary unpooled DataSources.
+	</li>
+      </ul>
+      <p>
+	The library tries hard to get the details right:
+      </p>
+      <ul>
+	<li>
+	  c3p0 DataSources are both <tt>Referenceable</tt> and <tt>Serializable</tt>, and are thus
+	  suitable for binding to a wide-variety of JNDI-based naming services.
+	</li>
+	<li>
+	  Statement and ResultSets are carefully cleaned up when pooled
+	  Connections and Statements are checked in, to prevent resource-
+	  exhaustion when clients use the lazy but common resource-management
+	  strategy of only cleaning up their Connections....
+	</li>
+	<li>
+	  The library adopts the approach 
+	  defined by the JDBC 2 and 3 specification (even where these
+	  conflict with the library author's preferences). DataSources
+	  are written in the JavaBean style, offering all the required and
+	  most of the optional properties (as well as some non-standard ones),
+	  and no-arg constructors. All JDBC-defined internal interfaces are
+	  implemented (ConnectionPoolDataSource, PooledConnection,
+	  ConnectionEvent-generating Connections, etc.)
+	  You can mix c3p0 classes with compliant third-party implementations
+	  (although not all c3p0 features will work with external implementations). 
+	</li>
+      </ul>
+      <p>
+	c3p0 hopes to provide DataSource implementations more than suitable for 
+	use by high-volume "J2EE enterprise applications". Please provide feedback, bug-fixes, etc.!
+      </p>
+    </div>
+    <h2><a name="prerequisites">Prerequisites</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	c3p0 requires a level 1.3.x or above Java Runtime Environment, and
+	the JDBC 2.x or above javax.sql libraries. c3p0 works fine under
+	Java 1.4.x and 1.5.x as well.
+      </p>
+    </div>
+    <h2><a name="installation">Installation</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	Put the file <tt>lib/c3p0- at c3p0.version@.jar</tt>
+	somewhere in your CLASSPATH (or any other place where your application's
+	classloader will find it). That's it!
+      </p>
+    </div>
+    <h2><a name="using_c3p0">Using c3p0</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	From a users' perspective, c3p0 simply provides standard jdbc2 DataSource
+	objects. When creating these DataSources, users can control pooling-related, 
+	naming-related, and other properties (See <a href="#configuration_properties">Appendix A</a>). All pooling is entirely
+	transparent to users once a DataSource has been created.
+      </p>
+      <p>
+	There are three ways of acquiring c3p0 pool-backed DataSources: 1) directly instantiate and configure a 
+	<a href="apidocs/com/mchange/v2/c3p0/ComboPooledDataSource.html"><tt>ComboPooledDataSource</tt></a> bean; 
+	2) use the DataSources factory class; or 3) "build your own" pool-backed
+	DataSource by directly instantiating <tt>PoolBackedDataSource</tt> and setting its <tt>ConectionPoolDataSource</tt>. Most
+	users will probably find instantiating <a href="apidocs/com/mchange/v2/c3p0/ComboPooledDataSource.html"><tt>ComboPooledDataSource</tt></a>
+	to be the most convenient approach. Once instantiated,
+	c3p0 DataSources can be bound to nearly any JNDI-compliant name service.
+      </p>
+      <p>
+	Regardless of how you create your DataSource, c3p0 will use defaults for any configuration parameters that
+	you do not specify programmatically. c3p0 has built-in, hard-coded defaults, but you can override these by creating
+	a file called <tt>c3p0.properties</tt> and storing it as a top-level resource in the same <tt>CLASSPATH</tt> (or ClassLoader)
+	that loads c3p0's jar file. As of c3p0-0.9.1, you can also supply a file called <tt>c3p0-config.xml</tt> for more advanced
+	configuration. See <a href="#configuration">Configuration</a> below.
+      </p>
+      <h3>
+	<a name="using_combopooleddatasource">Instantiating and Configuring a ComboPooledDataSource</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	Perhaps the most straightforward way to create a c3p0 pooling DataSource is to instantiate an instance of 
+	<a href="apidocs/com/mchange/v2/c3p0/ComboPooledDataSource.html"><tt>com.mchange.v2.c3p0.ComboPooledDataSource</tt></a>. 
+	This is a JavaBean-style class with a public, no-arg constructor,
+	but before you use the DataSource, you'll have to be sure to set at least the property <tt>jdbcUrl</tt>. You may also
+	want to set <tt>user</tt> and <tt>password</tt>, and if you have not externally preloaded the old-style JDBC driver you'll
+	use you should set the <tt>driverClass</tt>.
+      </p>
+<div class="example">
+ComboPooledDataSource cpds = new ComboPooledDataSource();
+cpds.setDriverClass( "org.postgresql.Driver" ); //loads the jdbc driver            
+cpds.setJdbcUrl( "jdbc:postgresql://localhost/testdb" );
+cpds.setUser("swaldman");                                  
+cpds.setPassword("test-password");                                  
+	
+// the settings below are optional -- c3p0 can work with defaults
+cpds.setMinPoolSize(5);                                     
+cpds.setAcquireIncrement(5);
+cpds.setMaxPoolSize(20);
+	
+// The DataSource cpds is now a fully configured and usable pooled DataSource
+	
+...
+</div>
+      <p>
+	The defaults of any c3p0 DataSource are determined by configuration you supply, or
+	else revert to hard-coded defaults [see <a href="#configuration_properties">configuration properties</a>].
+	c3p0-0.9.1 and above supports <a href="#c3p0-config.xml">multiple, named configurations</a>.
+	If you wish to use a named configuration, construct your 
+	<a href="apidocs/com/mchange/v2/c3p0/ComboPooledDataSource.html"><tt>com.mchange.v2.c3p0.ComboPooledDataSource</tt></a>
+	with the configuration name as a constructor agument:
+      </p>
+      <div class="example">
+ComboPooledDataSource cpds = new ComboPooledDataSource("intergalactoApp");
+      </div>
+      <p>
+	Of course, you can still override any configuration properties programmatically, as above.
+      </p>
+      <h3>
+	<a name="using_datasources_factory">Using the DataSources factory class</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	Alternatively, you can use the static factory class 
+	<a href="apidocs/com/mchange/v2/c3p0/DataSources.html"><tt>com.mchange.v2.c3p0.DataSources</tt></a> to build unpooled DataSources 
+	from traditional JDBC drivers, and to build pooled DataSources from unpooled DataSources:
+      </p>
+      <div class="example">
+DataSource ds_unpooled = DataSources.unpooledDataSource("jdbc:postgresql://localhost/testdb", 
+                                                        "swaldman", 
+                                                        "test-password");
+DataSource ds_pooled = DataSources.pooledDataSource( ds_unpooled );
+
+// The DataSource ds_pooled is now a fully configured and usable pooled DataSource.
+// The DataSource is using a default pool configuration, and Postgres' JDBC driver
+// is presumed to have already been loaded via the jdbc.drivers system property or an
+// explicit call to Class.forName("org.postgresql.Driver") elsewhere.
+	
+...
+      </div>
+      <p>
+	If you use the <a href="apidocs/com/mchange/v2/c3p0/DataSources.html">DataSources</a>
+	factory class, and you want to programmatically override default configuration
+	parameters, you can supply a map of override properties:
+      </p>
+      <div class="example">
+DataSource ds_unpooled = DataSources.unpooledDataSource("jdbc:postgresql://localhost/testdb", 
+                                                        "swaldman", 
+                                                        "test-password");
+							      
+Map overrides = new HashMap();
+overrides.put("maxStatements", "200");         //Stringified property values work
+overrides.put("maxPoolSize", new Integer(50)); //"boxed primitives" also work
+
+// create the PooledDataSource using the default configuration and our overrides
+ds_pooled = DataSources.pooledDataSource( ds_unpooled, overrides ); 
+
+// The DataSource ds_pooled is now a fully configured and usable pooled DataSource,
+// with Statement caching enabled for a maximum of up to 200 statements and a maximum
+// of 50 Connections.
+
+...
+      </div>
+      <p>
+	If you are using <a href="#c3p0-config.xml">named configurations</a>, you can specify the configuration
+	that defines the default configuration for your DataSource:
+      </p>
+      <div class="example">
+// create the PooledDataSource using the a named configuration and specified overrides
+ds_pooled = DataSources.pooledDataSource( ds_unpooled, "intergalactoAppConfig", overrides ); 
+      </div>
+
+      <p><a id="showDataSourcesWithPoolConfig" 
+	    style="display: block"
+	    href="#" 
+	    onClick="return toggleDisplay('showDataSourcesWithPoolConfig', 'DataSourcesWithPoolConfig');"
+	    >Show deprecated PoolConfig approach...</a></p>
+      <div class="deprecated" style="display: none" id="DataSourcesWithPoolConfig">
+	<h5>Deprecated! Programmatic configuration via PoolConfig</h5>
+	<p>
+	  If you use the <a href="apidocs/com/mchange/v2/c3p0/DataSources.html">DataSources</a>
+	  factory class, and you want to programmatically override default configuration
+	  parameters, make use of the <a href="apidocs/com/mchange/v2/c3p0/PoolConfig.html"><tt>PoolConfig</tt></a> class:
+	</p>
+	<div class="example">
+DataSource ds_unpooled = DataSources.unpooledDataSource("jdbc:postgresql://localhost/testdb", 
+                                                        "swaldman", 
+                                                        "test-password");
+							      
+PoolConfig pc = new PoolConfig();
+pc.setMaxStatements(200);  //turn on Statement pooling
+
+// pass our overriding PoolConfig to the DataSources.pooledDataSource() factory method.
+
+ds_pooled = DataSources.pooledDataSource( ds_unpooled, pc ); 
+
+// The DataSource ds_pooled is now a fully configured and usable pooled DataSource,
+// with Statement caching enabled for a maximum of up to 200 statements.
+
+...
+	</div>
+	<a id="hideDataSourcesWithPoolConfig" 
+	   href="#" 
+	   onClick="return toggleDisplay('showDataSourcesWithPoolConfig', 'DataSourcesWithPoolConfig');"
+	   >Hide deprecated PoolConfig approach</a>
+      </div>
+  
+      <div class="boxed">
+	<a name="forceOverrideBox" />
+	<h4>RARE: Forcing authentication information, regardless of (mis)configuration of the underlying (unpooled) DataSource</h4>
+	<p>
+	  You can wrap any DataSouce using <tt>DataSource.pooledDataSource( ... )</tt>, usually with no
+	  problem whatsoever. DataSources are supposed to indicate the username and password associated
+	  by default with Connections via standard properties <tt>user</tt> and <tt>password</tt>. 
+	  Some DataSource implementations do not offer these properties. Usually this is not at all
+	  a problem. <tt>c3p0</tt> works around this
+	  by acquiring "default" Connections from the DataSource if it can't find default authentication
+	  information, and a client has not specified the authentification information via 
+	  <tt>getConnection( user, password )</tt>.
+	</p>
+	<p>
+	  However, in rare circumstances, non-c3p0 unpooled DataSources provide a <tt>user</tt> property, 
+	  but not a password property, or you have access to a DataSource that you wish to wrap behind a pool,
+	  but you wish to override its build-in authentification defaults without actually modifying the <tt>user</tt>
+	  or <tt>password</tt> properties.
+	</p>
+	<p>
+	  <tt>c3p0</tt> provides configuation properties <tt>overrideDefaultUser</tt> and <tt>overrideDefaultPassword</tt>.
+	  If you set these properties, programmatically as above, or via any of c3p0's <a href="#configuration">configuration mechanisms</a>,
+	  <tt>c3p0</tt> PooledDataSources will ignore the user and password property associated with the underlying DataSource,
+	  and use the specified overrides instead.
+	</p>
+      </div>
+      <h3>
+	<a name="querying">Querying a PooledDataSource's current status</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	c3p0 DataSources backed by a pool, which include implementations of 
+	<a href="apidocs/com/mchange/v2/c3p0/ComboPooledDataSource.html"><tt>ComboPooledDataSource</tt></a> and
+	the objects returned by <tt><a href="apidocs/com/mchange/v2/c3p0/DataSources.html">DataSources</a>.pooledDataSource( ... )</tt>, 
+	all implement the interface
+	<a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html"><tt>com.mchange.v2.c3p0.PooledDataSource</tt></a>, 
+	which makes available a number of methods for querying the status of
+	DataSource Connection pools. Below is sample code that queries a DataSource for its 
+	status:
+      </p>
+      <div class="example">
+// fetch a JNDI-bound DataSource
+InitialContext ictx = new InitialContext();
+DataSource ds = (DataSource) ictx.lookup( "java:comp/env/jdbc/myDataSource" );
+
+// make sure it's a c3p0 PooledDataSource
+if ( ds instanceof PooledDataSource)
+{
+  PooledDataSource pds = (PooledDataSource) ds;
+  System.err.println("num_connections: "      + pds.getNumConnectionsDefaultUser());
+  System.err.println("num_busy_connections: " + pds.getNumBusyConnectionsDefaultUser());
+  System.err.println("num_idle_connections: " + pds.getNumIdleConnectionsDefaultUser());
+  System.err.println();
+}
+else
+  System.err.println("Not a c3p0 PooledDataSource!");
+      </div>
+      <p>
+	The status querying methods all come in three overloaded forms, such as:
+      </p>
+      <ul>
+	<li><tt>public int getNumConnectionsDefaultUser()</tt></li>
+	<li><tt>public int getNumConnections(String username, String password)</tt></li>
+	<li><tt>public int getNumConnectionsAllUsers()</tt></li>
+      </ul>
+      <p>
+	c3p0 maintains separate pools for Connections with distinct
+	authentications. The various methods let you query the status of pools individually,
+	or aggregate statistics for all authentifications for which your DataSource is maintaining
+	pools. <i>Note that pool configuration parmeters such as <tt>maxPoolSize</tt> are enforced
+	  on a per-authentification basis!</i> For example, if you have set <tt>maxPoolSize</tt> to
+	20, and if the DataSource is managing connections under two username-password pairs [the
+	default, and one other pair established via a call to <tt>getConnection(user, password)</tt>, 
+	you should expect to see as many as 40 Connections from <tt>getNumConnectionsAllUsers()</tt>. 
+      </p>
+      <p>
+	<i>
+	  Most applications only acquire default-authenticated Connections from DataSources, and
+	  can typically just use the <tt>getXXXDefaultUser()</tt> to gather Connection statistics.
+	</i>
+      </p>
+      <p>
+        As well as Connection pool realted statistics, you can retrieve status information about each
+        DataSource's Thread pool.
+      	Please see <a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html"><tt>PooledDataSource</tt> 
+      	for a complete list of available operations.</a>
+      </p>
+      <div class="boxed">
+	<a name="using_c3p0_registry_box" />
+	<h4>Using C3P0Registry to get a reference to a DataSource</h4>
+	<p>
+	  If it's inconvenient or impossible to get a reference to your DataSource via JNDI or some other means,
+	  you can find all live c3p0 DataSources using the
+	  <a href="apidocs/com/mchange/v2/c3p0/C3P0Registry.html"><tt>C3P0Registry</tt></a> class, which includes
+	  three static methods to help you out:
+	</p>
+	<ul>
+	  <li><tt>public static Set getPooledDataSources()</tt></li>
+	  <li><tt>public static Set pooledDataSourcesByName( String dataSourceName )</tt></li>
+	  <li><tt>public static <a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html">PooledDataSource</a>  
+              pooledDataSourceByName( String dataSourceName )</tt></li>
+	</ul>
+	<p>
+	  The first method will hand you the Set of all live c3p0 
+	  <a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html">PooledDataSources</a>. If you are sure
+	  your application only makes one <a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html">PooledDataSources</a>,
+	  or you can distinguish between the DataSources by their configuration properties (inspected via "getters"), the 
+	  first method may be sufficient. Because this will not always be the case, c3p0 
+	  <a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html">PooledDataSources</a> have a special property called
+	  <tt>dataSourceName</tt>. You can set the <tt>dataSourceName</tt> property directly when you construct your 
+	  DataSource, or dataSourceName can be set like any other property in a named or the default config. 
+	  Otherwise, <tt>dataSourceName</tt> will default to either 1) the name of your DataSource's configuration, if
+	  you constructed it with a <a href="#c3p0-config.xml">named configuration</a>; or 2) a unique (but unpredicatble)
+	  name if you are using the default configuration.
+	</p>
+	<p>
+	  There is no guarantee that a <tt>dataSourceName</tt> will be unique. For example, if two c3p0 DataSources share
+	  the same <a href="#c3p0-config.xml">named configuration</a>, and you have not set the <tt>dataSourceName</tt> programmatically, the two
+	  data sources will both share the name of the configuration. To get all of the DataSources with a particular
+	  <tt>dataSourceName</tt>, use <tt>pooledDataSourcesByName( ... )</tt>. If you've ensured that your DataSource's
+	  name is unique (as you will generally want to do, if you intend to use 
+	  <a href="apidocs/com/mchange/v2/c3p0/C3P0Registry.html"><tt>C3P0Registry</tt></a> to find your DataSources),
+	  you can use the convenience method <tt>pooledDataSourceByName( ... )</tt>, which will return your DataSource
+	  directly, or <tt>null</tt> if no DataSource with that name is available. If you use <tt>pooledDataSourceByName( ... )</tt>
+	  and more than one DataSource shares the name supplied, which one it will return is undefined.
+	</p>
+      </div>
+      <h3>
+	<a name="cleaning">Cleaning up after c3p0 PooledDataSources</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	The easy way to clean up after c3p0-created DataSources is to use the static destroy method
+	defined by the class <a href="apidocs/com/mchange/v2/c3p0/DataSources.html">DataSources</a>. Only 
+	<a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html"><tt>PooledDataSource</tt></a>s 
+	need to be cleaned up, but 
+	<a href="apidocs/com/mchange/v2/c3p0/DataSources.html">DataSources</a>.destroy( ... ) does no harm if it is called on an unpooled or non-c3p0
+	DataSource.
+      </p>
+      <div class="example">
+DataSource ds_pooled   = null;
+	
+try
+{
+  DataSource ds_unpooled = DataSources.unpooledDataSource("jdbc:postgresql://localhost/testdb", 
+                                                          "swaldman", 
+                                                          "test-password");
+  ds_pooled = DataSources.pooledDataSource( ds_unpooled );
+
+  // do all kinds of stuff with that sweet pooled DataSource...
+}
+finally
+{
+  DataSources.destroy( ds_pooled );
+}
+      </div>
+      <p>
+	Alternatively, c3p0's <a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html"><tt>PooledDataSource</tt></a>
+	interface contains a <tt>close()</tt> method
+	that you can call when you know you are finished with a DataSource. So, you can cast a c3p0
+	derived DataSource to a <tt>PooledDataSource</tt> and close it:
+      </p>
+  <div class="example">
+static void cleanup(DataSource ds) throws SQLException
+{
+  // make sure it's a c3p0 PooledDataSource
+  if ( ds instanceof PooledDataSource)
+  {
+    PooledDataSource pds = (PooledDataSource) ds;
+    pds.close();
+  }     
+  else
+    System.err.println("Not a c3p0 PooledDataSource!");
+}
+  </div>
+      <p>
+	Unreferenced instances of <a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html"><tt>PooledDataSource</tt></a>
+	that are not <tt>close()</tt>ed by clients
+	<tt>close()</tt> themselves prior to garbage collection in their <tt>finalize()</tt> methods. 
+	As always, finalization should be considered
+	a backstop and not a prompt or sure approach to resource cleanup.
+      </p>
+      <h3>
+	<a name="build_your_own">Advanced: Building your own PoolBackedDataSource</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	There is little reason for most programmers to do this, but you can build a PooledDataSource in a
+	step-by-step way by instantiating and configuring an unpooled 
+	<a href="apidocs/com/mchange/v2/c3p0/DriverManagerDataSource.html"><tt>DriverManagerDataSource</tt></a>, instantiating a
+	<a href="apidocs/com/mchange/v2/c3p0/WrapperConnectionPoolDataSource.html"><tt>WrapperConnectionPoolDataSource</tt></a>
+	 and setting the unpooled DataSource as its <tt>nestedDataSource</tt> property,
+	and then using that to set the <tt>connectionPoolDataSource</tt> property of a new 
+	<a href="apidocs/com/mchange/v2/c3p0/PoolBackedDataSource.html"><tt>PoolBackedDataSource</tt></a>.
+      </p>
+      <p>
+	This sequence of events is primarily interesting if your driver offers an implementation of ConnectionPoolDataSource, and you'd
+	like c3p0 to use that. Rather than using c3p0's 
+	<a href="apidocs/com/mchange/v2/c3p0/WrapperConnectionPoolDataSource.html"><tt>WrapperConnectionPoolDataSource</tt></a>, 
+	you can create a <a href="apidocs/com/mchange/v2/c3p0/PoolBackedDataSource.html"><tt>PoolBackedDataSource</tt></a>
+	and set its <tt>connectionPoolDataSource</tt> property. Statement pooling, 
+	<a href="apidocs/com/mchange/v2/c3p0/ConnectionCustomizer.html"><tt>ConnectionCustomizers</tt></a>, and many c3p0-specific properties
+	are unsupported with third party implementations of <tt>ConnectionPoolDataSource</tt>. (Third-party <tt>DataSource</tt> implementations
+	can be substituted for c3p0's 
+	<a href="apidocs/com/mchange/v2/c3p0/DriverManagerDataSource.html"><tt>DriverManagerDataSource</tt></a> with no significant loss of
+	functionality.)
+      </p>
+      <h3>
+	<a name="raw_connection_ops">Advanced: Raw Connection and Statement Operations</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	JDBC drivers sometimes define vendor-specific, non-standard API on Connection and Statement implementations. C3P0 wraps
+	these Objects behind a proxies, so you cannot cast C3P0-returned Connections or Statements to the vendor-specific implementation
+	classes. C3P0 does not provide any means of accessing the raw Connections and Statements directly, because C3P0 needs to keep
+	track of Statements and ResultSets created in order to prevent resource leaks and pool
+	corruption.
+      </p>
+      <p>
+	C3P0 does provide an API that allows you to invoke non-standard methods reflectively on an underlying
+	Connection. To use it, first cast the returned Connection to a 
+	<a href="apidocs/com/mchange/v2/c3p0/C3P0ProxyConnection.html"><tt>C3P0ProxyConnection</tt></a>. Then call
+	the method <tt>rawConnectionOperation</tt>, supplying the <tt>java.lang.reflect.Method</tt> object for
+	the non-standard method you wish to call as an argument. The <tt>Method</tt> you supply will be invoked
+	on the target you provide on the second argument (null for static methods), and using the arguments you
+	supply in the third argument to that function. For the target, and for any of the method arguments, you
+	can supply the special token <tt>C3P0ProxyConnection.RAW_CONNECTION</tt>, which will be replaced with
+	the underlying vendor-specific Connection object before the <tt>Method</tt> is invoked.
+      </p>
+      <p>
+	<a href="apidocs/com/mchange/v2/c3p0/C3P0ProxyStatement.html"><tt>C3P0ProxyStatement</tt></a> offers
+	an exactly analogous API.
+      </p>
+      <p>
+	Any Statements (including Prepared and CallableStatements) and ResultSets returned by raw operations
+	will be c3p0-managed, and will be properly cleaned-up on <tt>close()</tt> of the parent proxy Connection.
+	Users must take care to clean up any non-standard resources returned by a vendor-specific method.
+      </p>
+      <p>
+	Here's an example of using Oracle-specific API to call a static method on a raw Connection:
+      </p>
+      <div class="example">
+C3P0ProxyConnection castCon = (C3P0ProxyConnection) c3p0DataSource.getConnection();
+Method m = CLOB.class.getMethod("createTemporary", new Class[]{Connection.class, boolean.class, int.class});
+Object[] args = new Object[] {C3P0ProxyConnection.RAW_CONNECTION, Boolean.valueOf( true ), new Integer( 10 )};
+CLOB oracleCLOB = (CLOB) castCon.rawConnectionOperation(m, null, args);			
+      </div>
+      <p>
+	<i>Note: C3P0 now includes special support for some Oracle-specific methods. 
+	  See <a href="#oracle-specific">Appendix F</a>.
+	</i>
+      </p>
+      <h2><a name="configuration">Configuration</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+      <h3>
+	<a name="programmatic_configuration"></a><a name="configuration_introduction">Introduction</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	While c3p0 does not <i>require</i> very much configuration, it is very tweakable. Most of the interesting
+	knobs and dials are represented as JavaBean properties. Following JavaBean conventions, we note that
+	if an Object has a property of type <tt>T</tt> called <tt>foo</tt>, it will have methods that look
+	like...
+	<blockquote><tt>
+	    public T getFoo();<br/>
+	    public void setFoo(T foo);
+	</tt></blockquote>
+	...or both, depending upon whether the property is read-only, write-only, or read-writable.
+      </p>
+      <p>
+	There are several ways to modify c3p0 properties: 
+	You can directly alter the property values associated with a particular DataSource in your code, 
+	or you can configure c3p0 externally, 
+	via a <a href="#c3p0_properties">simple Java properties file</a>,
+	via an <a href="#c3p0-config.xml">XML configuration file</a>, or  
+	via <a href="#system_properties">System properties</a>. Configuration files
+	are normally looked up under standard names (<tt>c3p0.properties</tt> or <tt>c3p0-config.xml</tt>)
+	at the top level of an application's classpath, but the XML configuration can be placed
+	anywhere in an application's file system, if the system property com.mchange.v2.c3p0.cfg.xml is set
+	(to an absolute filesystem path).
+      </p>
+      <p>
+	DataSources are usually configured before they are used, either
+	during or immediately following their construction. c3p0 does
+	support property modifications midstream, however.
+      </p>
+      <p>
+	If you obtain a DataSource by instantiating a 
+	<a href="apidocs/com/mchange/v2/c3p0/ComboPooledDataSource.html"><tt>ComboPooledDataSource</tt></a>, 
+	configure it by simply calling appropriate setter methods offered by that class
+	before attempting a call to <tt>getConnection()</tt>. See the example above.
+      </p>
+      <p>
+	If you obtain a DataSource by using factory methods of
+	the utility class <a href="apidocs/com/mchange/v2/c3p0/DataSources.html"><tt>com.mchange.v2.c3p0.DataSources</tt></a>,
+	and wish to use a non-default configuration, you can supply a Map of property names (beginning with lower-case letters)
+	to property values (either as Strings or "boxed" Java primitives like Integer or Boolean).
+      </p>
+      <p>
+	All tweakable properties are documented for reference
+	in <a href="#configuration_properties">Appendix A</a>. The most basic and important c3p0 configuration
+	topics are discussed below.
+      </p>
+      <h3>
+	<a name="basic_pool_configuration">Basic Pool Configuration</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	c3p0 Connection pools are very easy to configure via the following basic parameters:
+      </p>
+      <ul>
+	<li><a class="cfg_param" href="#acquireIncrement">acquireIncrement</a></li>
+	<li><a class="cfg_param" href="#initialPoolSize">initialPoolSize</a></li>
+	<li><a class="cfg_param" href="#maxPoolSize">maxPoolSize</a></li>
+	<li><a class="cfg_param" href="#maxIdleTime">maxIdleTime</a></li>
+	<li><a class="cfg_param" href="#minPoolSize">minPoolSize</a></li>
+      </ul>
+      <p>
+	<tt>initialPoolSize</tt>, <tt>minPoolSize</tt>, <tt>maxPoolSize</tt>
+	define the number of Connections that will be pooled. Please ensure that
+	<tt>minPoolSize <= maxPoolSize</tt>. Unreasonable values of <tt>initialPoolSize</tt> will
+	be ignored, and <tt>minPoolSize</tt> will be used instead.
+      </p>
+      <p>
+	Within the range between <tt>minPoolSize</tt> and <tt>maxPoolSize</tt>, the number of Connections in
+	a pool varies according to usage patterns. The number of Connections increases whenever a Connection
+	is requested by a user, no Connections are available, and the pool has not yet reached <tt>maxPoolSize</tt>
+	in the number of Connections managed. Since Connection acquisition is very slow, it is almost always useful to
+	increase the number of Connections eagerly, in batches, rather than forcing each client to wait for a new
+	Connection to provoke a single acquisition when the load is increasing. <tt>acquireIncrement</tt> determines
+	how many Connections a c3p0 pool will attempt to acquire when the pool has run out of Connections. (Regardless
+	of <tt>acquireIncrement</tt>, the pool will never allow <tt>maxPoolSize</tt> to be exceeded.)
+      </p>
+      <p>
+	The number of Connections in a pool decreases whenever a pool tests a Connection and finds it to be broken (see
+	<a href="#configuring_connection_testing">Configuring Connection Testing</a> below), or when a Connection is expired
+	by the pool after sitting idle for a period of time, or for being too old (See <a href="#managing_pool_size">Managing Pool Size and Connection Age</a>.)
+      </p>
+      <h3>
+	<a name="managing_pool_size">Managing Pool Size and Connection Age</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	Different applications have different needs with regard to trade-offs between performance, footprint, and reliability. C3P0
+	offers a wide variety of options for controlling how quickly pools that have grown large under load revert to <tt>minPoolSize</tt>,
+	and whether "old" Connections in the pool should be proactively replaced to maintain their reliablity.
+      </p>
+      <ul>
+	<li><a class="cfg_param" href="#maxConnectionAge">maxConnectionAge</a></li>
+	<li><a class="cfg_param" href="#maxIdleTime">maxIdleTime</a></li>
+	<li><a class="cfg_param" href="#maxIdleTimeExcessConnections">maxIdleTimeExcessConnections</a></li>
+      </ul>
+	 <p>
+    By default, pools will never expire Connections. If you wish
+	Connections to be expired over time in order to maintain "freshness", 
+	set <tt>maxIdleTime</tt> and/or <tt>maxConnectionAge</tt>. <tt>maxIdleTime</tt> defines how many seconds a
+	Connection should be permitted to go unused before being culled from the pool. <tt>maxConnectionAge</tt>
+	forces the pool to cull any Connections that were acquired from the database more than the set number of
+	seconds in the past.
+      </p>
+      <p>
+    <tt>maxIdleTimeExcessConnections</tt> is about minimizing the number of Connections held by c3p0 pools
+    when the pool is not under load. By default, c3p0 pools grow under load, but only shrink if Connections
+    fail a Connection test or are expired away via the parameters described above. Some users want their
+    pools to quickly release unnecessary Connections after a spike in usage that forces a large pool size.
+    You can achieve this by setting <tt>maxIdleTimeExcessConnections</tt> to a value much shorter than 
+    <tt>maxIdleTime</tt>, forcing Connections beyond your set minimum size to be released if they sit idle
+    for more than a short period of time.
+      </p>
+      <p>
+    Some general advice about all of these timeout parameters: Slow down! The point of Connection pooling is to
+    bear the cost of acquiring a Connection only once, and then to reuse the Connection many, many times.
+    Most databases support Connections that remain open for hours at a time. There's no need to churn through
+    all your Connections every few seconds or minutes. Setting <tt>maxConnectionAge</tt> or <tt>maxIdleTime</tt> to 1800 (30 minutes)
+    is quite aggressive. For most databases, several hours may be more appropriate. You can ensure the reliability
+    of your Connections by testing them, rather than by tossing them. (see
+	<a href="#configuring_connection_testing">Configuring Connection Testing</a>.) The only one of these parameters
+    that should generally be set to a few minutes or less is <tt>maxIdleTimeExcessConnections</tt>.
+      </p>
+      <h3>
+	<a name="configuring_connection_testing">Configuring Connection Testing</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	c3p0 can be configured to test the Connections that it pools in a variety of ways, to
+	minimize the likelihood that your application will see broken or "stale" Connections.
+	Pooled Connections can go bad for a variety of reasons -- some JDBC drivers intentionally
+	"time-out" long-lasting database Connections; back-end databases or networks sometimes go down 
+	"stranding" pooled Connections; and Connections can simply become corrupted over time and use due
+	to resource leaks, driver bugs, or other causes.
+      </p>
+      <p>
+	c3p0 provides users a great deal of flexibility in testing Connections, via the following
+	configuration parameters:
+      </p>
+      <ul>
+	<li><a class="cfg_param" href="#automaticTestTable">automaticTestTable</a></li>
+	<li><a class="cfg_param" href="#connectionTesterClassName">connectionTesterClassName</a></li>
+	<li><a class="cfg_param" href="#idleConnectionTestPeriod">idleConnectionTestPeriod</a></li>
+	<li><a class="cfg_param" href="#preferredTestQuery">preferredTestQuery</a></li>
+	<li><a class="cfg_param" href="#testConnectionOnCheckin">testConnectionOnCheckin</a></li>
+	<li><a class="cfg_param" href="#testConnectionOnCheckout">testConnectionOnCheckout</a></li>
+      </ul>
+      <p>
+	<tt>idleConnectionTestPeriod</tt>, <tt>testConnectionOnCheckout</tt>, and
+	<tt>testConnectionOnCheckin</tt> control <u>when</u> Connections will be tested.
+	<tt>automaticTestTable</tt>, <tt>connectionTesterClassName</tt>, and <tt>preferredTestQuery</tt> control <u>how</u> they will be tested.
+      </p>
+      <p>
+	When configuring
+	Connection testing, first try to minimize the cost of each test. By default, Connections are tested
+	by calling the <tt>getTables()</tt> method on a Connection's associated <tt>DatabaseMetaData</tt>
+	object. This has the advantage of working with any database, and regardless of the database schema. However, empirically
+	a <tt>DatabaseMetaData.getTables()</tt> call is often much slower than a simple database query.
+      </p>
+      <p>
+	The most convenient way to speed up Connection testing is to define the parameter <tt>automaticTestTable</tt>. Using the name
+	you provide, c3p0 will create an empty table, and make a simple query against it to test the database. Alternatively, if your 
+	database schema
+	is fixed prior to your application's use of the database, you can simply define a test query with the <tt>preferredTestQuery</tt>
+	parameter. Be careful, however. Setting <tt>preferredTestQuery</tt> will lead to errors as Connection tests fail
+	if the query target table does not exist in your database table <i>prior to initialization of your DataSource</i>.
+	  </p>
+	  <p>
+	Advanced users may define any kind of Connection testing they wish, by implementing a 
+	<a href="apidocs/com/mchange/v2/c3p0/ConnectionTester.html">ConnectionTester</a> and supplying the
+	fully qualified name of the class as <tt>connectionTesterClassName</tt>. If you'd like your custom ConnectionTesters
+	to honor and support the <tt>preferredTestQuery</tt> and <tt>automaticTestTable</tt> parameters, implement
+	<a href="apidocs/com/mchange/v2/c3p0/UnifiedConnectionTester.html">UnifiedConnectionTester</a>, most conveniently by extending
+	<a href="apidocs/com/mchange/v2/c3p0/AbstractConnectionTester.html">AbstractConnectionTester</a>. See the <a href="apidocs/index.html">api docs</a>
+	for more information.
+      </p>
+      <p>
+	The most reliable time to test Connections is on check-out. But this is also the most costly choice
+	from a client-performance perspective. Most applications should work quite reliably using a combination of 
+	<tt>idleConnectionTestPeriod</tt> and <tt>testConnectionsOnCheckIn</tt>. Both the idle test and the check-in 
+	test are performed asynchronously, which leads to better performance, both perceived and actual.
+      </p>
+      <p>
+	Note that for many applications, high performance is more important than the risk of an occasional database exception.
+	In its default configuration, c3p0 does no Connection testing at all. Setting a fairly long 
+	<tt>idleConnectionTestPeriod</tt>, and not testing on checkout and check-in at all is an excellent, high-performance
+	approach.
+      </p>
+      <h3>
+	<a name="configuring_statement_pooling">Configuring Statement Pooling</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	c3p0 implements transparent PreparedStatement pooling as defined by the JDBC spec. Under some circumstances,
+	statement pooling can dramatically improve application performance. Under other circumstances, the overhead of
+	statement pooling can slightly harm
+	performance. Whether and how much statement pooling will help depends on how much 
+	parsing, planning, and optimizing of queries your databases does when the statements are prepared. 
+	Databases (and JDBC drivers) vary widely
+	in this respect. It's a good idea to benchmark your application with and without statement pooling to 
+	see if and how much it helps.
+      </p>
+      <p>
+	You configure statement pooling in c3p0 via the following
+	configuration parameters:
+      </p>
+      <ul>
+	<li><a class="cfg_param" href="#maxStatements">maxStatements</a></li>
+	<li><a class="cfg_param" href="#maxStatementsPerConnection">maxStatementsPerConnection</a></li>
+      </ul>
+      <p>
+	<tt>maxStatements</tt> is JDBC's standard parameter for controlling statement pooling. <tt>maxStatements</tt> defines the
+	total number <tt>PreparedStatements</tt> a DataSource will cache. The pool will destroy the least-recently-used PreparedStatement
+	when it hits this limit. This sounds simple, but it's actually a strange approach, because
+	cached statements conceptually belong to individual Connections; they are not global resources. To figure out a size
+	for <tt>maxStatements</tt> that does not "churn" cached statements, you need to consider the number of <i>frequently used</i> 
+	PreparedStatements in your application,	and multiply that by the number of Connections you expect in the pool (<tt>maxPoolSize</tt>
+	in a busy application).
+      </p>
+      <p>
+	<tt>maxStatementsPerConnection</tt> is a non-standard configuration parameter that makes a bit more
+	sense conceptually. It defines how many statements each pooled Connection is allowed to own.
+	You can set this to a bit more than the number of <tt>PreparedStatements</tt> your application <i>frequently</i>
+	uses, to avoid churning.
+      </p>
+      <p>
+	If either of these parameters are greater than zero, statement pooling will be enabled. If both
+	parameters are greater than zero, both limits will be enforced. If only one is greater than zero, statement pooling
+	will be enabled, but only one limit will be enforced.
+      </p>
+      <h3>
+	<a name="configuring_recovery">Configuring Recovery From Database Outages</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	c3p0 DataSources are designed (and configured by default) to recover from temporary database outages, such as
+	those which occur during a database restart or brief loss of network connectivity.
+	You can affect how c3p0 handles errors in acquiring Connections via the following
+	configurable properties:
+      </p>
+      <ul>
+	<li><a class="cfg_param" href="#acquireRetryAttempts">acquireRetryAttempts</a></li>
+	<li><a class="cfg_param" href="#acquireRetryDelay">acquireRetryDelay</a></li>
+	<li><a class="cfg_param" href="#breakAfterAcquireFailure">breakAfterAcquireFailure</a></li>
+      </ul>
+      <p>
+	When a c3p0 DataSource attempts and fails to acquire a Connection, it will retry up
+	to <tt>acquireRetryAttempts</tt> times, with a delay of <tt>acquireRetryDelay</tt>
+	between each attempt. If all attempts fail, any clients waiting for Connections from
+	the DataSource will see an Exception, indicating that a Connection could not be acquired.
+	Note that clients do not see any Exception until a full round of attempts fail, which
+	may be some time after the initial Connection attempt. If <tt>acquireRetryAttempts</tt> 
+	is set to 0, c3p0 will attempt to acquire new Connections indefinitely, and calls to
+	<tt>getConnection()</tt> may block indefinitely waiting for a successful acquisition.
+      </p>
+      <p>
+	Once a full round of acquisition attempts fails, there are two possible policies. By
+	default, the c3p0 DataSource will remain active, and will try again to acquire Connections
+	in response to future requests for Connections. If you set <tt>breakAfterAcquireFailure</tt>
+	to <tt>true</tt>, the DataSource will consider itself broken after a failed round of
+	Connection attempts, and future client requests will fail immediately.
+      </p>
+      <p>
+	Note that if a database restart occurs, a pool may contain previously acquired but now
+	stale Connections. By default, these stale Connections will only be detected and
+	purged lazily, when an application attempts to use them, and sees an Exception. Setting
+	<tt>maxIdleTime</tt> or <tt>maxConnectionAge</tt> can help speed up the replacement of
+	broken Connections. (See <a href="#managing_pool_size">Managing ConnectionAge</a>.)
+	If you wish to avoid application Exceptions entirely, you must adopt a connection testing strategy that
+	is likely to detect stale Connections prior to their delivery to clients. (See 
+	"<a href="#configuring_connection_testing">Configuring Connection Testing</a>".) Even
+	with active Connection testing (<tt>testConnectionsOnCheckout</tt> set to <tt>true</tt>, or
+	<tt>testConnectionsOnCheckin</tt> and a short <tt>idleConnectionTestPeriod</tt>), your
+	application may see occasional Exceptions on database restart, for example if the restart 
+	occurs after a Connection to the database has already been checked out.
+      </p>
+      <h3>
+	<a name="connection_customizers">Managing Connection Lifecycles with Connection Customizer</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	Application frequently wish to set up Connections in some standard, reusable way immediately after
+	Connection acquisitions. Examples of this include setting-up character encodings, or date and time
+	related behavior, using vendor-specific APIs or non-standard SQL statement executions. Occasionally
+	it is useful to override the default values of standard Connection properties such as <tt>transactionIsolation</tt>,
+	<tt>holdability</tt>, or <tt>readOnly</tt>. c3p0 provides a "hook" interface that you can implement,
+	which gives you the opportunity to modify or track Connections just after they are checked out from the
+	database, immediately just prior to being handed to clients on checkout, just prior to being returned
+	to the pool on check-in, and just prior to final destruction by the pool. The Connections handed
+	to ConnectionCustomizers are raw, physical Connections, with all vendor-specific API accessible.
+	See the API docs for <a href="apidocs/com/mchange/v2/c3p0/ConnectionCustomizer.html"><tt>ConnectionCustomizer</tt></a>.
+      </p>
+      <p>
+    To install a <tt>ConnectionCustomizer</tt> just implement the interface, make your class accessible
+    to c3p0's ClassLoader, and set the configuration parameter below:
+      </p>
+      <ul>
+	<li><a class="cfg_param" href="#connectionCustomizerClassName">connectionCustomizerClassName</a></li>
+      </ul>
+      <p>
+	ConnectionCustomizers are required to be immutable classes with public no argument constructors.
+	They shouldn't store any state. For (rare) applications that wish to track the behavior of individual
+	DataSources with ConnectionCustomizers, the lifecycle methods each accept a DataSource-specific 
+	"identityToken", which is unique to each PooledDataSource.
+      </p>
+      <p>
+	Below is a sample <tt>ConnectionCustomizer</tt>. Implementations that do not need to override all
+	four <tt>ConnectionCustomizer</tt> methods can extend 
+	<a href="apidocs/com/mchange/v2/c3p0/AbstractConnectionCustomizer.html"><tt>AbstractConnectionCustomizer</tt></a>
+	to inherit no-op implementations of all methods.  
+      </p>
+      <div class="example">
+import com.mchange.v2.c3p0.*;
+import java.sql.Connection;
+
+public class VerboseConnectionCustomizer
+{
+    public void onAcquire( Connection c, String pdsIdt )
+    { 
+       System.err.println("Acquired " + c + " [" + pdsIdt + "]"); 
+
+       // override the default transaction isolation of 
+       // newly acquired Connections
+       c.setTransactionIsolation( Connection.REPEATABLE_READ );
+    }
+
+    public void onDestroy( Connection c, String pdsIdt )
+    { System.err.println("Destroying " + c + " [" + pdsIdt + "]"); }
+
+    public void onCheckOut( Connection c, String pdsIdt )
+    { System.err.println("Checked out " + c + " [" + pdsIdt + "]"); }
+
+    public void onCheckIn( Connection c, String pdsIdt )
+    { System.err.println("Checking in " + c + " [" + pdsIdt + "]"); }
+}
+      </div>
+
+      <h3>
+	<a name="configuring_unresolved">Configuring Unresolved Transaction Handling</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	Connections checked into a pool cannot have any unresolved transactional work associated with them.
+	If users have set <tt>autoCommit</tt> to <tt>false</tt> on a Connection, and c3p0 cannot guarantee
+	that there is no pending transactional work, c3p0 must either <tt>rollback()</tt> or <tt>commit()</tt>
+	on check-in (when a user calls <tt>close()</tt>). The JDBC spec is (unforgivably) silent on the question
+	of whether unresolved work should be committed or rolled back on Connection close. By default, c3p0
+	rolls back unresolved transactional work when a user calls <tt>close()</tt>.
+      </p>
+      <p>
+	You can adjust this behavior via the following configuration properties:
+      </p>
+      <ul>
+	<li><a class="cfg_param" href="#autoCommitOnClose">autoCommitOnClose</a></li>
+	<li><a class="cfg_param" href="#forceIgnoreUnresolvedTransactions">forceIgnoreUnresolvedTransactions</a></li>
+      </ul>
+      <p>
+	If you wish c3p0 to allow unresolved transactional work to commit on checkin, set <tt>autoCommitOnClose</tt>
+	to true. If you wish c3p0 to leave transaction management to you, and neither commit nor rollback (nor modify
+	the state of Connection <tt>autoCommit</tt>), you may set <tt>forceIgnoreUnresolvedTransactions</tt> to true. Setting
+	<tt>forceIgnoreUnresolvedTransactions</tt> is strongly discouraged, because if clients are not careful to
+	commit or rollback themselves prior to close(), or do not set Connection <tt>autoCommit</tt> consistently, bizarre
+	unreproduceable behavior and database lockups can occur.
+      </p>
+      <h3>
+	<a name="configuring_to_debug_and_workaround_broken_clients">Configuring to Debug and Workaround Broken Client Applications</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	Sometimes client applications are sloppy about close()ing all Connections they check out. Eventually, 
+	the pool grows to <tt>maxPoolSize</tt>, and then runs out of Connections, because of these bad clients.
+      </p>
+      <p>
+    The right way to address this problem is to fix the client application. <tt>c3p0</tt> can help you debug,
+    by letting you know where Connections are checked out that occasionally don't get checked in. In rare and
+    unfortunate situations, development of the client application is closed, and even though it is buggy, you cannot fix it.
+    c3p0 can help you work around the broken application, preventing it from exhausting the pool. 
+      </p>
+      <p>
+    The following parameters can help you debug or workaround broken client applications.  
+      </p>
+      <ul>
+	<li><a class="cfg_param" href="#debugUnreturnedConnectionStackTraces">debugUnreturnedConnectionStackTraces</a></li>
+	<li><a class="cfg_param" href="#unreturnedConnectionTimeout">unreturnedConnectionTimeout</a></li>
+      </ul>
+      <p>
+    <tt>unreturnedConnectionTimeout</tt> defines a limit (in seconds) to how long a Connection may remain checked out.
+    If set to a nozero value, unreturned, checked-out Connections that exceed this limit will be summarily destroyed,
+    and then replaced in the pool. 
+    Obviously, you must take care to set this parameter to a value large enough that all intended operations
+    on checked out Connections have time to complete.
+    You can use this parameter to merely workaround unreliable client apps that fail to
+    close() Connections. 
+      </p>
+      <p>
+    Much better than working-around is fixing. If, <i>in addition to setting</i> <tt>unreturnedConnectionTimeout</tt>,
+    you set <tt>debugUnreturnedConnectionStackTraces</tt> to <tt>true</tt>,
+	then a stack trace will be captured
+    each time a Connection is checked-out. Whenever an unreturned Connection times out, that stack trace will be
+    printed, revealing where a Connection was checked out that was not checked in promptly. <tt>debugUnreturnedConnectionStackTraces</tt>
+    is intended to be used only for debugging, as capturing a stack trace can slow down Connection check-out.
+      </p>
+      <h3>
+	<a name="other_ds_configuration">Other DataSource Configuration</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	See <a href="#configuration_properties">Appendix A</a> for information about the following configuration properties:
+      </p>
+      <ul>
+	<li><a class="cfg_param" href="#checkoutTimeout">checkoutTimeout</a></li>
+	<li><a class="cfg_param" href="#factoryClassLocation">factoryClassLocation</a></li>
+	<li><a class="cfg_param" href="#maxAdministrativeTaskTime">maxAdministrativeTaskTime</a></li>
+	<li><a class="cfg_param" href="#numHelperThreads">numHelperThreads</a></li>
+	<li><a class="cfg_param" href="#usesTraditionalReflectiveProxies">usesTraditionalReflectiveProxies</a></li>
+      </ul>
+      <p>
+      	<tt>numHelperThreads</tt> and <tt>maxAdministrativeTaskTime</tt> help to configure the behavior
+      	of DataSource thread pools. By default, each DataSource has only three associated helper threads.
+      	If performance seems to drag under heavy load, or if you observe via JMX or direct inspection of
+      	a <tt>PooledDataSource</tt>, that the number of "pending tasks" is usually greater than zero, try
+      	increasing <tt>numHelperThreads</tt>. <tt>maxAdministrativeTaskTime</tt> may be useful for users
+      	experiencing tasks that hang indefinitely and "APPARENT DEADLOCK" messages. (See Appendix A for more.)
+      </p>
+      <p>
+      	<tt>checkoutTimeout</tt> limits how long a client will wait for a Connection, if all Connections are
+      	checked out and one cannot be supplied immediately. <tt>usesTraditionalReflectiveProxies</tt>, which is
+      	of little practical use, permits you to use an old, now superceded implementation of C3P0-generated proxy objects. (C3P0
+      	used to use reflective, dynamic proxies. Now, for enhanced performance, it uses code-generated, nonrefective
+      	implementations.) <tt>factoryClassLocation</tt> can be used to indicate where a URL from which c3p0 classes
+      	can be downloaded, if c3p0 DataSources will be retrieved as References from a JNDI DataSource by clients
+      	who do not have c3p0 locally installed.
+      </p>
+      <h3>
+	<a name="jmx_configuration_and_management">Configuring and Managing c3p0 via JMX</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	If JMX libraries and a JMX MBeanServer are available in your environment (they are include in JDK 1.5 and above), 
+	you can inspect and configure your c3p0 datasources via a JMX administration tool (such as jconsole, bundled with
+	jdk 1.5). You will find that c3p0 registers MBeans under <tt>com.mchange.v2.c3p0</tt>, one with statistics about the
+	library as a whole (called <tt>C3P0Registry</tt>), and an MBean for each <tt>PooledDataSource</tt> you deploy. You can view and
+	modify your DataSource's configuration properties, track the activity of Connection, Statement, and Thread pools, and reset
+	pools and DataSources via the <tt>PooledDataSource</tt> MBean. (You may wish to view the API docs of 
+	<a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html"><tt>PooledDataSource</tt></a> for documentation of 
+	the available operations.)
+      </p>
+      <p>
+    If you do not want c3p0 to register MBeans with your JMX environment, you can suppress this
+    behavior with the following, set either as a System property or in <tt>c3p0.properties</tt>:
+      </p>
+      <div class="example">
+com.mchange.v2.c3p0.management.ManagementCoordinator=com.mchange.v2.c3p0.management.NullManagementCoordinator      
+      </div>
+      <h3>
+	<a name="configuring_logging">Configuring Logging</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	c3p0 uses a custom logging library similar to jakarta commons-logging. Log messages can be directed to
+	the popular log4j logging library, to the standard logging facility introduced with jdk1.4, or to
+	<tt>System.err</tt>. Nearly all configuration should be done at the level of your preferred logging
+	library. There are a very few configuration options specific to c3p0's logging, and usually the defaults
+	will be fine. Logging-related parameters
+	may be placed in your <tt>c3p0.properties</tt> file, in a file called <tt>mchange-log.properties</tt> at
+	the top-level of your classpath, or they may be defined as System properties. (The logging properties defined
+	below may <u>not</u> be defined in <tt>c3p0-config.xml</tt>!) See the 
+	<a href="#log_properties_box">box</a> below.
+      </p>
+      <p>
+	c3p0's logging behavior is affected by certain build-time options. If build-option <tt>c3p0.debug</tt> is set
+	to <tt>false</tt>, all messages at a logging level below INFO will be suppressed. Build-option <tt>c3p0.trace</tt> controls how fine-grained c3p0's below
+	INFO level reporting will be. For the moment, distributed
+	c3p0 binaries are compiled with <tt>debug</tt> set to <tt>true</tt> and <tt>trace</tt> set to its maximum level of <tt>10</tt>.
+	But binaries may eventually be
+	distributed with <tt>debug</tt> set to <tt>false</tt>. (For the moment, the performance impact of the logging level-checks seems
+	very small, and it's most flexible to compile in all the messages, and let your logging library control which are emitted.) When
+	c3p0 starts up, it emits the build-time values of debug and trace, along with the version and build time.
+      </p>
+      <dl class="log_properties">
+	<a name="log_properties_box"></a>
+	<dt><a name="com.mchange.v2.log.MLog" />com.mchange.v2.log.MLog</dt>
+	<dd>
+	  <div class="propdesc">
+	    Determines which library c3p0 will output log messages to. By default, if log4j is available,
+	    it will use that library, otherwise if jdk1.4 logging apis are available it will use those,
+	    and if neither are available, it will use a simple fallback that logs to <tt>System.err</tt>.
+	    If you want to directly control which library is used, you may set this property to one of:
+	    <ul>
+	      <li><tt>com.mchange.v2.log.log4j.Log4jMLog</tt></li>
+	      <li><tt>com.mchange.v2.log.jdk14logging.Jdk14MLog</tt></li>
+	      <li><tt>com.mchange.v2.log.FallbackMLog</tt></li>
+	    </ul>
+	    You may also set this property to a comma separated list of the above alternatives, to
+	    define an order of preference among logging libraries.
+	  </div>
+	</dd>
+	<dt><a name="com.mchange.v2.log.NameTransformer" />com.mchange.v2.log.NameTransformer</dt>
+	<dd>
+	  <div class="propdesc">
+	    By default, c3p0 uses very fine-grained logging, in general with one logger for each
+	    c3p0 class. For a variety of reasons, some users may prefer fewer, more global loggers.
+	    You may opt for one-logger-per-package by setting <tt>com.mchange.v2.log.NameTransformer</tt>
+	    to the value <tt>com.mchange.v2.log.PackageNames</tt>. Advanced users can also define 
+	    other strategies for organizing the number and names of loggers by setting this variable
+	    to the fully-qualified class name of a custom implementation of the 
+	    <tt>com.mchange.v2.log.NameTransformer</tt> interface.
+	  </div>
+	</dd>
+	<dt><a name="com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL" />com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL</dt>
+	<dd>
+	  <div class="propdesc">
+	    If, whether by choice or by necessity, you are using c3p0's <tt>System.err</tt> fallback logger, you can
+	    use this parameter to control how detailed c3p0's logging should be. Any of the following values (taken
+	    from the jdk1.4 logging library) are acceptable:
+	    <ul>
+	      <li><tt>OFF</tt></li>
+	      <li><tt>SEVERE</tt></li>
+	      <li><tt>WARNING</tt></li>
+	      <li><tt>INFO</tt></li>
+	      <li><tt>CONFIG</tt></li>
+	      <li><tt>FINE</tt></li>
+	      <li><tt>FINER</tt></li>
+	      <li><tt>FINEST</tt></li>
+	      <li><tt>ALL</tt></li>
+	    </ul>
+	    This property defaults to <tt>INFO</tt>.
+	  </div>
+	</dd>
+      </dl>
+    </div>
+    <h2><a name="performance">Performance</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	Enhanced performance is the purpose of Connection and Statement pooling, and a
+	major goal of the c3p0 library. For most applications, Connection pooling
+	will provide a significant performance gain, especially if you are acquiring
+	an unpooled Connection for each client access. If you are letting a single,
+	shared Connection serve many clients to avoid Connection acquisition overhead, 
+	you may suffer performance issues and problems managing transactions when 
+	your Connection is under concurrent load; Connection pooling will enable you 
+	to switch to a one Connection-per-client model with little or no cost. 
+	If you are writing Enterprise Java Beans, you may be tempted to acquire a 
+	Connection once and not return it until the bean is about to be destroyed or
+	passivated. But this can be resource-costly, as dormant pooled 
+	beans needlessly hold the Connection's network and database resources. 
+	Connection pooling permits beans to only "own" 
+	a Connection while they are using it.  
+      </p>
+      <p>
+	But, there are performance costs to c3p0 as well. In order to implement 
+	automatic cleanup of unclosed <tt>ResultSets</tt> and <tt>Statements</tt> when parent resources 
+	are returned to pools, all client-visible <tt>Connections</tt>, <tt>ResultSets</tt>, <tt>Statements</tt> 
+	are really wrappers around objects provided by an underlying unpooled DataSource 
+	or "traditional" JDBC driver. Thus, there is some extra overhead to all JDBC calls.
+      </p>
+      <p>
+	Some attention has been paid to minimizing the "wrapper" overhead of c3p0. In
+	my environment, the wrapper overhead amounts from several hundreths to several
+	thousandths of the cost of Connection acquisition, so unless you are making
+	many, many JDBC calls in fast succession, there will be a net 
+	gain in performance and resource-utilization efficiency. 
+	Significantly, the overhead associated with ResultSet operations (where
+	one might iterate through a table with thousands of records) appears to be 
+	negligibly small.
+      </p>
+    </div>
+    <h2><a name="known_shortcomings">Known Shortcomings</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <ul>
+	<li>
+	  <p>
+	    Connections and Statements are pooled on a per-authentication basis.
+	    So, if one pool-backed DataSource is used to acquire Connections both
+	    for [<tt>user</tt>=alice, <tt>password</tt>=secret1] and [<tt>user</tt>=bob, <tt>password</tt>=secret2],
+	    there will be two distinct pools, and the DataSource might in the
+	    worst case manage twice the number of Connections specified by the
+	    <tt>maxPoolSize</tt> property.
+	  </p>
+	  <p>
+	    This fact is a natural consequence of the definition of the DataSource spec (which
+	    allows Connections to be acquired with multiple user authentications), and the
+	    requirement that all Connections in a single pool be functionally identical.
+	    This "issue" will not be changed or fixed. It's noted here just so you understand
+	    what's going on.
+	  </p>
+	</li>
+	<li>
+	  <p>
+	    The overhead of Statement pooling is too high. For drivers that
+	    do not perform significant preprocessing of PreparedStatements, the
+	    pooling overhead outweighs any savings. Statement pooling is thus
+	    turned off by default. If your driver does preprocess <tt>PreparedStatements</tt>,
+	    especially if it does so via IPC with the RDBMS, you will probably
+	    see a significant performance gain by turning Statement pooling on. (Do this by
+	    setting the configuration property <tt>maxStatements</tt> or <tt>maxStatementsPerConnection</tt>
+	    to a value greater than zero.).
+	  </p>
+	</li>
+      </ul>
+    </div>
+    <h2><a name="feedback_and_support">Feedback and Support</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	Please provide any and all feedback to <<a href="mailto:swaldman at mchange.com">swaldman at mchange.com</a>&gt! 
+	Also, feel free to join and ask questions on the <tt>c3p0-users</tt> mailing list.
+	Sign up at <a href="http://sourceforge.net/projects/c3p0/">http://sourceforge.net/projects/c3p0/</a>
+      </p>
+      <p>  
+	Thank you for using c3p0!!!
+      </p>
+    </div>
+    <h2><a name="configuration_properties">Appendix A: Configuration Properties</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	c3p0 configuration properties can be divided into <a href="#javabeans-style-properties">JavaBeans-style Properties</a> and
+	<a href="#other-properties">Other Properties</a>.
+      </p>
+      <h3>
+	<a name="javabeans-style-properties">JavaBeans-style Properties</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	The following properties can be set directly in code as JavaBeans properties, 
+	via a <a href="#system_properties">System properties</a> or a <a href="#c3p0_properties"><tt>c3p0.properties</tt></a>  file
+	(with <tt>c3p0.</tt> prepended to
+	the property name), or in a <a href="#c3p0-config.xml"><tt>c3p0-config.xml</tt></a> file. See the section on 
+	<a href="#configuration">Configuration</a> above.
+	Click on the property name for a full description. 
+      </p>
+      <table class="beanPropSummaryTable">
+	<tr>
+	  <td>
+	    <a href="#acquireIncrement">acquireIncrement</a><br/>
+	    <a href="#acquireRetryAttempts">acquireRetryAttempts</a><br/>
+	    <a href="#acquireRetryDelay">acquireRetryDelay</a><br/>
+	    <a href="#autoCommitOnClose">autoCommitOnClose</a><br/>
+	    <a href="#automaticTestTable">automaticTestTable</a><br/>
+	    <a href="#breakAfterAcquireFailure">breakAfterAcquireFailure</a><br/>
+	    <a href="#checkoutTimeout">checkoutTimeout</a><br/>
+	    <a href="#connectionCustomizerClassName">connectionCustomizerClassName</a><br/>
+	    <a href="#connectionTesterClassName">connectionTesterClassName</a><br/>
+	    <a href="#debugUnreturnedConnectionStackTraces">debugUnreturnedConnectionStackTraces</a><br/>
+	    <a href="#factoryClassLocation">factoryClassLocation</a><br/>
+	  </td>
+	  <td>
+	    <a href="#forceIgnoreUnresolvedTransactions">forceIgnoreUnresolvedTransactions</a><br/>
+	    <a href="#idleConnectionTestPeriod">idleConnectionTestPeriod</a><br/>
+	    <a href="#initialPoolSize">initialPoolSize</a><br/>
+	    <a href="#maxAdministrativeTaskTime">maxAdministrativeTaskTime</a><br/>
+	    <a href="#maxConnectionAge">maxConnectionAge</a><br/>
+	    <a href="#maxIdleTime">maxIdleTime</a><br/>
+	    <a href="#maxIdleTimeExcessConnections">maxIdleTimeExcessConnections</a><br/>
+	    <a href="#maxPoolSize">maxPoolSize</a><br/>
+	    <a href="#maxStatements">maxStatements</a><br/>
+	    <a href="#maxStatementsPerConnection">maxStatementsPerConnection</a><br/>
+	    <a href="#minPoolSize">minPoolSize</a><br/>
+	  </td>
+	  <td>
+	    <a href="#numHelperThreads">numHelperThreads</a><br/>
+	    <a href="#overrideDefaultUser">overrideDefaultUser</a><br/>
+	    <a href="#overrideDefaultPassword">overrideDefaultPassword</a><br/>
+	    <a href="#password">password</a><br/>
+	    <a href="#preferredTestQuery">preferredTestQuery</a><br/>
+	    <a href="#propertyCycle">propertyCycle</a><br/>
+	    <a href="#testConnectionOnCheckin">testConnectionOnCheckin</a><br/>
+	    <a href="#testConnectionOnCheckout">testConnectionOnCheckout</a><br/>
+	    <a href="#unreturnedConnectionTimeout">unreturnedConnectionTimeout</a><br/>
+	    <a href="#user">user</a><br/>
+	    <a href="#usesTraditionalReflectiveProxies">usesTraditionalReflectiveProxies</a><br/>
+	  </td>
+	</tr>
+      </table>
+      <dl class="properties">
+	<dt><a name="acquireIncrement" />acquireIncrement</dt>
+	<dd>
+	  <div class="default">Default: 3</div>
+	  <div class="propdesc">
+	    Determines how many connections at a time c3p0 will try to acquire when the pool is exhausted. 
+	    [See <a href="#basic_pool_configuration">"Basic Pool Configuration"</a>]
+	  </div>
+	</dd>
+	<dt><a name="acquireRetryAttempts" />acquireRetryAttempts</dt>
+	<dd>
+	  <div class="default">Default: 30</div>
+	  <div class="propdesc">
+	    Defines how many times c3p0 will try to acquire a new Connection from the database before giving up. If
+	    this value is less than or equal to zero, c3p0 will keep trying to fetch a Connection indefinitely.	  
+	    [See <a href="#configuring_recovery">"Configuring Recovery From Database Outages"</a>]
+	  </div>
+	</dd>
+	<dt><a name="acquireRetryDelay" />acquireRetryDelay</dt>
+	<dd>
+	  <div class="default">Default: 1000</div>
+	  <div class="propdesc">
+	    Milliseconds, time c3p0 will wait between acquire attempts.
+	    [See <a href="#configuring_recovery">"Configuring Recovery From Database Outages"</a>]
+	  </div>
+	</dd>
+	<dt><a name="autoCommitOnClose" />autoCommitOnClose</dt>
+	<dd>
+	  <div class="default">Default: false</div>
+	  <div class="propdesc">
+	    The JDBC spec is unforgivably silent on what should happen to unresolved, pending
+            transactions on Connection close. C3P0's default policy is to rollback any uncommitted, pending
+            work. (I think this is absolutely, undeniably the right policy, but there is no consensus among JDBC driver vendors.) 
+            Setting <tt>autoCommitOnClose</tt> to true causes uncommitted pending work to be committed, rather than rolled
+            back on Connection close. [<i>Note: Since the spec is absurdly unclear on this question, application authors who wish
+              to avoid bugs and inconsistent behavior should ensure that all transactions are explicitly either committed or
+              rolled-back before close is called.</i>]
+	    [See <a href="#configuring_unresolved">"Configuring Unresolved Transaction Handling"</a>]
+	  </div>
+	</dd>
+	<dt><a name="automaticTestTable" />automaticTestTable</dt>
+	<dd>
+	  <div class="default">Default: null</div>
+	  <div class="propdesc">
+	    If provided, c3p0 will create an empty table of the specified name, and use queries against that table to
+	    test the Connection. If <tt>automaticTestTable</tt> is provided, c3p0 will generate its own test query, therefore
+	    any <tt>preferredTestQuery</tt> set will be ignored. You should not work with the named table after c3p0 creates
+	    it; it should be strictly for c3p0's use in testing your Connection. (If you define your own ConnectionTester, it
+	    must implement the <a href="apidocs/com/mchange/v2/c3p0/QueryConnectionTester.html">QueryConnectionTester</a>
+	    interface for this parameter to be useful.) [See <a href="#configuring_connection_testing">"Configuring Connection Testing"</a>]
+	  </div>
+	</dd>
+	<dt><a name="breakAfterAcquireFailure" />breakAfterAcquireFailure</dt>
+	<dd>
+	  <div class="default">Default: false</div>
+	  <div class="propdesc">
+	    If true, a pooled DataSource will declare itself broken and be permanently closed if
+	    a Connection cannot be obtained from the database after making <tt>acquireRetryAttempts</tt> to acquire one.
+	    If false, failure to obtain a Connection will cause all Threads waiting for the pool to acquire a Connection
+	    to throw an Exception, but the DataSource will remain valid, and will attempt to acquire again following
+	    a call to <tt>getConnection()</tt>.
+	    [See <a href="#configuring_recovery">"Configuring Recovery From Database Outages"</a>]
+	  </div>
+	</dd>
+	<dt><a name="checkoutTimeout" />checkoutTimeout</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+	    The number of milliseconds a client calling getConnection() will wait for a Connection to be checked-in or acquired
+	    when the pool is exhausted. Zero means wait indefinitely. Setting any positive value will cause the getConnection()
+	    call to time-out and break with an <tt>SQLException</tt> after the specified number of milliseconds.
+	  </div>
+	</dd>
+	<dt><a name="connectionCustomizerClassName" />connectionCustomizerClassName</dt>
+	<dd>
+	  <div class="default">Default: null</div>
+	  <div class="propdesc">
+	    The fully qualified class-name of an implememtation of the <a href="apidocs/com/mchange/v2/c3p0/ConnectionCustomizer.html"><tt>ConnectionCustomizer</tt></a>
+	    interface, which users can implement to set up Connections when they are acquired from the database, or on check-out, and potentially
+	    to clean things up on check-in and Connection destruction. If standard Connection properties (holdability, readOnly, or transactionIsolation)
+	    are set in the ConnectionCustomizer's onAcquire() method, these will override the Connection default values.
+	  </div>
+	</dd>
+	<dt><a name="connectionTesterClassName" />connectionTesterClassName</dt>
+	<dd>
+	  <div class="default">Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester</div>
+	  <div class="propdesc">
+	    The fully qualified class-name of an implememtation of the <a href="apidocs/com/mchange/v2/c3p0/ConnectionTester.html"><tt>ConnectionTester</tt></a>
+	    interface, or <a href="apidocs/com/mchange/v2/c3p0/QueryConnectionTester.html"><tt>QueryConnectionTester</tt></a> if you would like instances
+	    to have access to a user-configured <tt>preferredTestQuery</tt>. This can be used to customize how c3p0 DataSources test Connections, but with
+	    the introduction of <tt>automaticTestTable</tt> and <tt>preferredTestQuery</tt> configuration parameters, "rolling your own" should be overkill
+	    for most users.
+	    [See <a href="#configuring_connection_testing">"Configuring Connection Testing"]</a>
+	  </div>
+	</dd>
+	<dt><a name="debugUnreturnedConnectionStackTraces" />debugUnreturnedConnectionStackTraces</dt>
+	<dd>
+	  <div class="default">Default: false</div>
+	  <div class="propdesc">
+		If true, and if <tt><a href="#unreturnedConnectionTimeout">unreturnedConnectionTimeout</a></tt> is set to a positive value,
+		then the pool will capture the stack trace (via an Exception) of all Connection checkouts, and the stack traces will be
+		printed when unreturned checked-out Connections timeout. This is intended to debug applications with Connection leaks, that
+		is applications that occasionally fail to return Connections, leading to pool growth, and eventually exhaustion (when the
+		pool hits <tt>maxPoolSize</tt> with all Connections checked-out and lost). This parameter should only be set while debugging,
+		as capturing the stack trace will slow down every Connection check-out.
+	  </div>
+	  <div class="per-user">Does Not Support Per-User Overrides.</div>
+	</dd>
+	<dt><a name="factoryClassLocation" />factoryClassLocation</dt>
+	<dd>
+	  <div class="default">Default: null</div>
+	  <div class="propdesc">
+	    DataSources that will be bound by JNDI and use that API's Referenceable interface
+            to store themselves may specify a URL from which the class capable of dereferencing 
+            a them may be loaded. If (as is usually the case) the c3p0 libraries will be locally
+            available to the JNDI service, leave this set as null.	
+	  </div>
+	  <div class="per-user">Does Not Support Per-User Overrides.</div>
+	</dd>
+	<dt><a name="forceIgnoreUnresolvedTransactions" />forceIgnoreUnresolvedTransactions</dt>
+	<dd>
+	  <div class="default">Default: false</div>
+	  <div class="propdesc">
+	    <b><i>Strongly disrecommended. Setting this to <tt>true</tt> may lead to subtle and bizarre bugs.</i></b>
+            This is a terrible setting, leave it alone unless absolutely necessary. It is here to workaround
+            broken databases / JDBC drivers that do not properly support transactions, but that allow Connections'
+            <tt>autoCommit</tt> flags to go to false regardless. If you are using a database that supports transactions
+            "partially" (this is oxymoronic, as the whole point of transactions is to perform operations reliably and
+            completely, but nonetheless such databases are out there), if you feel comfortable ignoring the fact that Connections
+            with <tt>autoCommit == false</tt> may be in the middle of transactions and may hold locks and other resources,
+            you may turn off c3p0's wise default behavior, which is to protect itself, as well as the usability and consistency
+            of the database, by either rolling back (default) or committing (see <tt>c3p0.autoCommitOnClose</tt> <i>above</i>)
+            unresolved transactions. <b>This should only be set to true when you are sure you are using a database that
+              allows Connections' autoCommit flag to go to false, but offers no other meaningful support of transactions. Otherwise
+              setting this to true is just a bad idea.</b>	
+	    [See <a href="#configuring_unresolved">"Configuring Unresolved Transaction Handling"</a>]
+	  </div>
+	</dd>
+	<dt><a name="idleConnectionTestPeriod"/>idleConnectionTestPeriod</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+	    If this is a number greater than 0, c3p0 will test all idle, pooled but unchecked-out connections, 
+	    every this number of seconds. [See <a href="#configuring_connection_testing">"Configuring Connection Testing"</a>]
+	  </div>
+	</dd>
+	<dt><a name="initialPoolSize"/>initialPoolSize</dt>
+	<dd>
+	  <div class="default">Default: 3</div>
+	  <div class="propdesc">
+	    Number of Connections a pool will try to acquire upon startup. Should be between <tt>minPoolSize</tt> and
+	    <tt>maxPoolSize</tt>.
+	    [See <a href="#basic_pool_configuration">"Basic Pool Configuration"</a>]
+	  </div>
+	</dd>
+	<dt><a name="maxAdministrativeTaskTime" />maxAdministrativeTaskTime</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+	    Seconds before c3p0's thread pool will try to interrupt an apparently hung task. <u>Rarely useful.</u> Many of c3p0's functions
+	    are not performed by client threads, but asynchronously by an internal thread pool. c3p0's asynchrony enhances
+	    client performance directly, and minimizes the length of time that critical locks are held by ensuring that slow
+	    jdbc operations are performed in non-lock-holding threads. If, however, some of these tasks "hang", that is
+	    they neither succeed nor fail with an Exception for a prolonged period of time, c3p0's thread pool can become
+	    exhausted and administrative tasks backed up. If the tasks are simply slow, the best way to resolve the problem
+	    is to increase the number of threads, via <a href="#numHelperThreads">numHelperThreads</a>. But if tasks
+	    sometimes hang indefinitely, you can use this parameter to force a call to the task thread's <tt>interrupt()</tt>
+	    method if a task exceeds a set time limit. [c3p0 will eventually recover from hung tasks anyway by signalling an "APPARENT
+	    DEADLOCK" (you'll see it as a warning in the logs), replacing the thread pool task threads, and interrupt()ing the
+	    original threads. But letting the pool go into APPARENT DEADLOCK and then recover means that for some periods,
+	    c3p0's performance will be impaired. So if you're seeing these messages, increasing <a href="#numHelperThreads">numHelperThreads</a>
+	    and setting <tt>maxAdministrativeTaskTime</tt> might help. <tt>maxAdministrativeTaskTime</tt> should be large enough
+	    that any resonable attempt to acquire a Connection from the database, to test a Connection, or two destroy a Connection, 
+	    would be expected to succeed or fail within the time set. Zero (the default) means tasks are never interrupted,
+	    which is the best and safest policy under most circumstances. If tasks are just slow, allocate more threads. If tasks
+	    are hanging forever, try to figure out why, and maybe setting <tt>maxAdministrativeTaskTime</tt> can help in the meantime.
+	  </div>
+	  <div class="per-user">Does Not Support Per-User Overrides.</div>
+	</dd>
+	<dt><a name="maxConnectionAge" />maxConnectionAge</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+	    Seconds, effectively a time to live. A Connection older than <tt>maxConnectionAge</tt> will be destroyed and 
+	    purged from the pool. This differs from <tt>maxIdleTime</tt> in that it refers to absolute age. Even a Connection
+	    which has not been much idle will be purged from the pool if it exceeds <tt>maxConnectionAge</tt>. Zero means
+	    no maximum absolute age is enforced.
+	  </div>
+	</dd>
+	<dt><a name="maxIdleTime" />maxIdleTime</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+	    Seconds a Connection can remain pooled but unused before being discarded. Zero means idle connections never expire.
+	    [See <a href="#basic_pool_configuration">"Basic Pool Configuration"</a>]
+	  </div>
+	</dd>
+	<dt><a name="maxIdleTimeExcessConnections" />maxIdleTimeExcessConnections</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+		Number of seconds that Connections in excess of <tt>minPoolSize</tt> should be permitted to remain idle in the pool
+		before being culled. Intended for applications that wish to aggressively minimize the number of open Connections,
+		shrinking the pool back towards minPoolSize if, following a spike, the load level diminishes and Connections
+		acquired are no longer needed. If <tt>maxIdleTime</tt> is set, <tt>maxIdleTimeExcessConnections</tt> should be
+		smaller if the parameter is to have any effect. Zero means no enforcement, excess Connections are not idled out.
+	  </div>
+	</dd>
+	<dt><a name="maxPoolSize"/>maxPoolSize</dt>
+	<dd>
+	  <div class="default">Default: 15</div>
+	  <div class="propdesc">
+	    Maximum number of Connections a pool will maintain at any given time.
+	    [See <a href="#basic_pool_configuration">"Basic Pool Configuration"</a>]
+	  </div>
+	</dd>
+	<dt><a name="maxStatements" />maxStatements</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+	    The size of c3p0's global PreparedStatement cache. If both <tt>maxStatements</tt> and <tt>maxStatementsPerConnection</tt>
+	    are zero, statement caching will not be enabled. If <tt>maxStatements</tt> is zero but <tt>maxStatementsPerConnection</tt>
+	    is a non-zero value, statement caching will be enabled, but no global limit will be enforced, only the per-connection maximum.
+	    <tt>maxStatements</tt> controls the total number of Statements cached,
+	    for all Connections. If set, it should be a fairly large number, as each pooled Connection requires its own,
+	    distinct flock of cached statements. As a guide, consider how many distinct PreparedStatements are used
+	    <i>frequently</i> in your application, and multiply that number by <tt>maxPoolSize</tt> to arrive at an appropriate
+	    value. Though <tt>maxStatements</tt> is the JDBC standard parameter for controlling statement caching, users may
+	    find c3p0's alternative <tt>maxStatementsPerConnection</tt> more intuitive to use.
+	    [See <a href="#configuring_statement_pooling">"Configuring Statement Pooling"</a>]
+	  </div>
+	</dd>
+	<dt><a name="maxStatementsPerConnection" />maxStatementsPerConnection</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+	    The number of PreparedStatements c3p0 will cache for a single pooled Connection. 
+	    If both <tt>maxStatements</tt> and <tt>maxStatementsPerConnection</tt>
+	    are zero, statement caching will not be enabled. If <tt>maxStatementsPerConnection</tt> is zero but <tt>maxStatements</tt>
+	    is a non-zero value, statement caching will be enabled, and a global limit enforced, but otherwise no limit will be set 
+	    on the number of cached statements for a single Connection.
+	    If set, maxStatementsPerConnection should be set to about the number distinct PreparedStatements that are used
+	    <i>frequently</i> in your application, plus two or three extra so infrequently statements don't force the more common
+	    cached statements to be culled. Though <tt>maxStatements</tt> is the JDBC standard parameter for controlling statement caching,
+	    users may find <tt>maxStatementsPerConnection</tt> more intuitive to use.
+	    [See <a href="#configuring_statement_pooling">"Configuring Statement Pooling"</a>]
+	  </div>
+	</dd>
+	<dt><a name="minPoolSize"/>minPoolSize</dt>
+	<dd>
+	  <div class="default">Default: 3</div>
+	  <div class="propdesc">
+	    Minimum number of Connections a pool will maintain at any given time.
+	    [See <a href="#basic_pool_configuration">"Basic Pool Configuration"</a>]
+	  </div>
+	</dd>
+	<dt><a name="numHelperThreads" />numHelperThreads</dt>
+	<dd>
+	  <div class="default">Default: 3</div>
+	  <div class="propdesc">
+	    c3p0 is very asynchronous. Slow JDBC operations are generally 
+            performed by helper threads that don't hold contended locks. Spreading
+            these operations over multiple threads can significantly improve performance
+            by allowing multiple operations to be performed simultaneously.	
+	  </div>
+	  <div class="per-user">Does Not Support Per-User Overrides.</div>
+	</dd>
+	<dt><a name="overrideDefaultUser" />overrideDefaultUser</dt>
+	<dd>
+	  <div class="default">Default: null</div>
+	  <div class="propdesc">
+	    Forces the username that should by PooledDataSources when a user calls the default
+	    getConnection() method. This is primarily useful when applications are pooling Connections
+	    from a non-c3p0 unpooled DataSource. Applications that use <tt>ComboPooledDataSource</tt>, 
+	    or that wrap any c3p0-implemented unpooled DataSource can use the simple 
+	    <a href="#user">user</a> property.
+<!--
+	    C3P0 PooledDataSources, implicitly or explicity, are wrappers around unpooled DataSources.
+	    For most users, the unpooled DataSource is c3p0's own DriverManagerDataSource, but users are
+	    welcome to wrap other DataSource implementations. Per the JDBC 2.0 spec, DataSources may offer
+	    standard properties "user" and "password". If present, c3p0 PooledDataSources use these properties 
+	    to determine what kind of authentication to use. If absent, PooledDataSources use the unpooled 
+	    DataSource's default getConnection() method. Some DataSources offer one but not both of the
+	    standard properties, which confuses c3p0.
+--> 
+	  </div>
+	  <div class="per-user">Does Not Support Per-User Overrides.</div>
+	</dd>
+	<dt><a name="overrideDefaultPassword" />overrideDefaultPassword</dt>
+	<dd>
+	  <div class="default">Default: null</div>
+	  <div class="propdesc">
+	    Forces the password that should by PooledDataSources when a user calls the default
+	    getConnection() method. This is primarily useful when applications are pooling Connections
+	    from a non-c3p0 unpooled DataSource. Applications that use <tt>ComboPooledDataSource</tt>, 
+	    or that wrap any c3p0-implemented unpooled DataSource can use the simple
+	    <a href="#password">password</a> property.
+	  </div>
+	  <div class="per-user">Does Not Support Per-User Overrides.</div>
+	</dd>
+	<dt><a name="password" />password</dt>
+	<dd>
+	  <div class="default">Default: null</div>
+	  <div class="propdesc">
+	    For applications using <tt>ComboPooledDataSource</tt> or any 
+	    c3p0-implemented unpooled DataSources — <tt>DriverManagerDataSource</tt> or the
+	    DataSource returned by <tt>DataSources.unpooledDataSource( ... )</tt> —
+	    defines the password that will be used for the DataSource's default 
+	    <tt>getConnection()</tt> method. (See also <a href="#user">user</a>.)
+	  </div>
+	  <div class="per-user">Does Not Support Per-User Overrides.</div>
+	</dd>
+	<dt><a name="preferredTestQuery" />preferredTestQuery</dt>
+	<dd>
+	  <div class="default">Default: null</div>
+	  <div class="propdesc">
+	    Defines the query that will be executed for all connection tests, if the default ConnectionTester (or some
+	    other implementation of <a href="apidocs/com/mchange/v2/c3p0/QueryConnectionTester.html">QueryConnectionTester</a>,
+	    or better yet <a href="apidocs/com/mchange/v2/c3p0/FullQueryConnectionTester.html">FullQueryConnectionTester</a>) 
+	    is being used. Defining a <tt>preferredTestQuery</tt>
+	    that will execute quickly in your database may dramatically speed up Connection tests. (If no <tt>preferredTestQuery</tt>
+	    is set, the default ConnectionTester executes a <tt>getTables()</tt> call on the Connection's DatabaseMetaData.
+	    Depending on your database, this may execute more slowly than a "normal" database query.) 
+	    <b>NOTE: The table against
+        which your <tt>preferredTestQuery</tt> will be run must exist in the database schema <i>prior</i> to your initialization
+        of your DataSource. If your application defines its own schema, try <tt>automaticTestTable</tt> instead.</b>
+        [See <a href="#configuring_connection_testing">"Configuring Connection Testing"</a>]
+	  </div>
+	</dd>
+	<dt><a name="propertyCycle" />propertyCycle</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+	    Maximum time in seconds before user configuration constraints are enforced.
+		Determines how frequently <tt>maxConnectionAge</tt>, <tt>maxIdleTime</tt>, <tt>maxIdleTimeExcessConnections</tt>,
+		<tt>unreturnedConnectionTimeout</tt> are enforced. c3p0 periodically checks the age of Connections to
+		see whether they've timed out. This parameter determines the period. Zero means automatic: A suitable period
+		will be determined by c3p0. [You can call <tt>getEffectivePropertyCycle...()</tt> methods on a c3p0
+		<a href="apidocs/com/mchange/v2/c3p0/PooledDataSource.html">PooledDataSource</a> to find the period
+		automatically chosen.]
+	  </div>
+	</dd>
+	<dt><a name="testConnectionOnCheckin" />testConnectionOnCheckin</dt>
+	<dd>
+	  <div class="default">Default: false</div>
+	  <div class="propdesc">
+            If true, an operation will be performed asynchronously at every connection checkin to verify that the connection is valid.
+	    Use in combination with <tt>idleConnectionTestPeriod</tt> for quite reliable, always asynchronous Connection testing.
+	    Also, setting an <tt>automaticTestTable</tt> or <tt>preferredTestQuery</tt> will usually speed up all connection tests.
+            [See <a href="#configuring_connection_testing">"Configuring Connection Testing"</a>]
+	  </div>
+	</dd>
+	<dt><a name="testConnectionOnCheckout" />testConnectionOnCheckout</dt>
+	<dd>
+	  <div class="default">Default: false</div>
+	  <div class="propdesc">
+	    <b><i>Use only if necessary. Expensive.</i></b>
+            If true, an operation will be performed at every connection checkout to verify that the connection is valid.
+            <b>Better choice:</b> verify connections periodically using <tt>idleConnectionTestPeriod</tt>. Also, setting an
+	    <tt>automaticTestTable</tt> or <tt>preferredTestQuery</tt> will usually speed up all connection tests.
+            [See <a href="#configuring_connection_testing">"Configuring Connection Testing"</a>]
+	  </div>
+	</dd>
+	<dt><a name="unreturnedConnectionTimeout" />unreturnedConnectionTimeout</dt>
+	<dd>
+	  <div class="default">Default: 0</div>
+	  <div class="propdesc">
+		Seconds. If set, if an application checks out but then fails to check-in [i.e. close()] a Connection
+		within the specified period of time, the pool will unceremoniously destroy() the Connection. This permits
+		applications with occasional Connection leaks to survive, rather than eventually exhausting the Connection
+		pool. And that's a shame. Zero means no timeout, applications are expected to close() their own Connections.
+		Obviously, if a non-zero value is set, it should be to a value longer than any Connection should reasonably
+		be checked-out. Otherwise, the pool will occasionally kill Connections in active use, which is bad. 
+	    <b><i>This is basically a bad idea, but it's a commonly requested feature. Fix your $%!@% applications
+	    so they don't leak Connections! Use this temporarily in combination with 
+	    <tt>debugUnreturnedConnectionStackTraces</tt> to figure out
+	    where Connections are being checked-out that don't make it back into the pool!</i></b>
+	  </div>
+	</dd>
+	<dt><a name="user" />user</dt>
+	<dd>
+	  <div class="default">Default: null</div>
+	  <div class="propdesc">
+	    For applications using <tt>ComboPooledDataSource</tt> or any 
+	    c3p0-implemented unpooled DataSources — <tt>DriverManagerDataSource</tt> or the
+	    DataSource returned by <tt>DataSources.unpooledDataSource()</tt> —
+	    defines the username that will be used for the DataSource's default 
+	    <tt>getConnection()</tt> method. (See also <a href="#password">password</a>.)
+	  </div>
+	  <div class="per-user">Does Not Support Per-User Overrides.</div>
+	<dt><a name="usesTraditionalReflectiveProxies" />usesTraditionalReflectiveProxies</dt>
+	<dd>
+	  <div class="default">Default: false</div>
+	  <div class="propdesc">
+	    c3p0 originally used reflective dynamic proxies for implementations of Connections and other JDBC
+	    interfaces. As of c3p0-0.8.5, non-reflective, code-generated implementations are used instead. As
+	    this was a major change, and the old codebase had been extensively used and tested, this parameter
+	    was added to allow users to revert of they had problems. The new, non-reflexive implementation is
+	    faster, and has now been widely deployed and tested, so it is unlikely that this parameter will be useful.
+	    Both the old reflective and newer non-reflective codebases are being maintained, but support for the
+	    older codebase may (or may not) be dropped in the future.
+ 	  </div>
+	</dd>
+      </dl>
+      <h3>
+	<a name="other-properties">Other Properties</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h3>
+      <p>
+	The following configuration properties affect the behavior of the c3p0 library as a whole. They
+	may be set as system properties, or in a <a href="#c3p0_properties"><tt>c3p0.properties</tt></a> file.
+      </p>
+      <div class="other_properties_desc">
+    <h4>Locating Configuration Information</h4>
+    <p>
+      Normally, c3p0's configuration information is placed in a either a c3p0-config.xml or c3p0.properties file
+      at the top-level of an application's CLASSPATH. However, if you wish to place configuration information
+      elsewhere, you may place c3p0 configuration information (in the <a href="#c3p0-config.xml">XML file format</a> only!) anywhere you'd like
+      in the filesystem visible to your application. Just set the following property to the full, absolute path
+      of the XML config file: 
+    </p>
+ 	<ul class="other_props_list">
+	  <li>com.mchange.v2.c3p0.cfg.xml</li>
+	</ul>
+	<h4>Logging-related properties</h4>
+	<p>
+	  The following properties affect c3p0's logging behavior. Please see <a href="#configuring_logging">Configuring Logging</a>
+	  above for specific information.
+	</p>
+	<ul class="other_props_list">
+	  <li>com.mchange.v2.log.MLog</li>
+	  <li>com.mchange.v2.log.NameTransformer</li>
+	  <li>com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL</li>
+	</ul>
+	<h4>Configuring JMX</h4>
+	<p>
+	  The following property controls c3p0's JMX management interface. Plese see 
+	  <a href="#jmx_configuration_and_management">Configuring and Managing c3p0 via JMX</a> above
+	  for more information.
+	</p>
+	<ul class="other_props_list">
+	  <li>com.mchange.v2.c3p0.management.ManagementCoordinator</li>
+	</ul>
+	<h4>Configuring the VMID</h4>
+	<p>
+	  Is it better to be beautiful or correct? Beginning with c3p0-0.9.1, c3p0 opts somewhat reluctantly for correctness.
+	</p>
+	<p>
+	  Here's the deal. Every c3p0 DataSource is allocated a unique "identity token", which is used to ensure that multiple
+	  JNDI lookups of the same PooledDataSource always return the same instance, even if the JNDI name-server stores a
+	  Serialized or Referenced instance. Previously, c3p0 was happy for generated IDs to be unique within a single VM (and it
+	  didn't even get that quite right, before c3p0-0.9.1). But in theory, one VM might look up two different DataSources,
+	  generated by two different VMs, that by unlikely coincidence have the same "identity token", leading to errors as one
+	  of the two DataSources sneakily substitutes for the second. Though this hypothetical issue has never been reported in practice,
+	  c3p0 resolves it by prepending a VMID to its identity tokens. This makes them long and ugly, but correct.
+	</p>
+	<p>
+	  If you don't like the long and ugly VMID, you can set your own, or you can turn off this solution to a hypothetical
+	  non-problem entirely with the following property:
+	</p>
+	<ul class="other_props_list">
+	  <li>com.mchange.v2.c3p0.VMID</li>
+	</ul>
+	<p>
+	  Set it to <tt>NONE</tt> to turn off the VMID, set it to <tt>AUTO</tt> to let c3p0 generate a VMID,
+	  or provide any other String to set the VMID that will be used directly. The default is <tt>AUTO</tt>.
+	</p>
+	<h4>Experimental properties</h4>
+	<p>
+	  c3p0-0.9.1 includes a new implementation of asynchronous Connection acquisition that
+	  should improve c3p0's performance and resource utilization in cases where database
+	  acquisition attempts, for whatever reason, occasionally fail. The new implementation
+	  should be significantly better than the "traditional" Connection acquisition strategy,
+	  but was added too late in the c3p0-0.9.1 development cycle to be fully tested and
+	  enabled by default. Users are encouraged to try the new implementation, both because
+	  it is better, and to help iron out any unanticipated problems.
+	</p>
+	<p>
+	  For a full description of the new implementation and the resource bottleneck it is
+	  designed to overcome, please see the <tt>CHANGELOG</tt> entry for <tt>c3p0-0.9.1-pre11</tt>.
+	  To enable the new implementation, set the following parameter to "<tt>true</tt>".
+	</p>
+	<ul class="other_props_list">
+	  <li>com.mchange.v2.resourcepool.experimental.useScatteredAcquireTask</li>
+	</ul>
+	<p>
+	  This feature is expected to be enabled by default in c3p0-0.9.2 and above.
+	</p>
+      </div>
+    </div>
+    <hr/>
+      <h2>
+	<a name="configuration_files">Appendix B: Configuration Files, etc.</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h2>
+      <div class="sectiontext">
+	<p>
+	  c3p0 configuration parameters can be set 
+	  <a href="#programmatic_configuration">directly in Java code</a>, 
+	  via a <a href="#c3p0_properties">simple Java properties file</a>,
+	  via an <a href="#c3p0-config.xml">XML configuration file</a>, or  
+	  via <a href="#system_properties">System properties</a>.
+	  Any which way
+	  works (the the XML configuration is most powerful, though, as it supports multiple named configurations and
+	  per-user overrides. Choose whatever works best for you.
+	</p>
+	<h3>
+	  <a name="c3p0_properties">Overriding c3p0 defaults via <tt>c3p0.properties</tt></a>
+	  <span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+	</h3>
+	<p>
+	  To override the library's built-in defaults, create a file called <tt>c3p0.properties</tt>
+	  and place it at the "root" of your classpath or classloader. For a typical standalone
+	  application, that means place the file in a directory named in your <tt>CLASSPATH</tt>
+	  environment variable. For a typical web-application, the file should be placed in 
+	  <tt>WEB-INF/classes</tt>. In general, the file must be available as a classloader
+	  resource under the name <tt>/c3p0.properties</tt>, in the classloader that loaded
+	  c3p0's jar file. Review the API docs (especilly <tt>getResource...</tt> methods) of 
+	  <tt>java.lang.Class</tt>, <tt>java.lang.ClassLoader</tt>, and <tt>java.util.ResourceBundle</tt> 
+	  if this is unfamiliar.
+	</p>
+	<p>
+	  The format of <tt>c3p0.properties</tt> should be a normal Java Properties file format,
+	  whose keys are c3p0 configurable properties. See <a href="#configuration_properties">Appendix A</a>.
+	  for the specifics. An example <tt>c3p0.properties</tt> file is produced below:
+	</p>
+	<div class="example">
+# turn on statement pooling
+c3p0.maxStatements=150
+	
+# close pooled Connections that go unused for
+# more than half an hour
+c3p0.maxIdleTime=1800
+	</div>
+	<h3>
+	  <a name="system_properties">Overriding c3p0 defaults with a System properties</a>
+	  <span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+	</h3>
+	<p>
+	  c3p0 properties can also be defined as System properties, using the same "c3p0." prefix for properties
+	  specified in a <tt>c3p0.properties</tt> file.
+	</p>
+	<div class="example">
+swaldman% java -Dc3p0.maxStatements=150 -Dc3p0.maxIdleTime=1800 example.MyC3P0App
+	</div>
+	<p>
+	  System properties override settings in c3p0.properties. Please see 
+	  <a href="#configuration_precedence">Precedence of Configuration Settings</a> for more information.
+	</p>
+	<h3>
+	  <a name="c3p0-config.xml">Named and Per-User configuration: Overriding c3p0 defaults via <tt>c3p0-config.xml</tt></a>
+	  <span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+	</h3>
+	<p>
+	  As of c3p0-0.9.1, you can define multiple configurations in an XML configuration file, and specify in your code which
+	  configuration to use. For any configurations (including the unnamed default configuration), you can define overrides
+	  for a particular database user. For example, if several applications access your database under different authentication
+	  credentials, you might define <tt>maxPoolSize</tt> to be 100 for user <tt>highVolumeApp</tt>, but only 10 for user 
+	  <tt>lowLoadApp</tt>. (Recall that Connections associated with different authentication credentials
+	  are of necessity separated into separate pools, so it makes sense that these could be configured separately.)
+	</p>
+	<p>
+	  You can use the XML config file for all c3p0 configuration, including configuration of defaults. However, for users who
+	  don't want or need the extra complexity, the c3p0.properties file will continue to be supported.
+	</p>
+	<p>
+	  By default, c3p0 will look for an XML configuration file in its classloader's resource path under the name "/c3p0-config.xml".
+	  That means the XML file should be placed in a directly or jar file directly named in your applications CLASSPATH, in WEB-INF/classes,
+	  or some similar location.
+	</p>
+	<p>
+	  If you prefer not to bundle your configuration with your code, you can specify an ordinary filesystem location for c3p0's
+	  configuration file via the system property <tt>com.mchange.v2.c3p0.cfg.xml</tt>.
+	</p>
+	<p>
+	  Here is an example <tt>c3p0-config.xml</tt> file:
+	</p>
+	<div class="example">
+<c3p0-config>
+  <default-config>
+    <property name="automaticTestTable">con_test</property>
+    <property name="checkoutTimeout">30000</property>
+    <property name="idleConnectionTestPeriod">30</property>
+    <property name="initialPoolSize">10</property>
+    <property name="maxIdleTime">30</property>
+    <property name="maxPoolSize">100</property>
+    <property name="minPoolSize">10</property>
+    <property name="maxStatements">200</property>
+
+    <user-overrides user="test-user">
+      <property name="maxPoolSize">10</property>
+      <property name="minPoolSize">1</property>
+      <property name="maxStatements">0</property>
+    </user-overrides>
+
+  </default-config>
+
+  <!-- This app is massive! -->
+  <named-config name="intergalactoApp"> 
+    <property name="acquireIncrement">50</property>
+    <property name="initialPoolSize">100</property>
+    <property name="minPoolSize">50</property>
+    <property name="maxPoolSize">1000</property>
+
+    <!-- intergalactoApp adopts a different approach to configuring statement caching -->
+    <property name="maxStatements">0</property> 
+    <property name="maxStatementsPerConnection">5</property>
+
+    <!-- he's important, but there's only one of him -->
+    <user-overrides user="master-of-the-universe"> 
+      <property name="acquireIncrement">1</property>
+      <property name="initialPoolSize">1</property>
+      <property name="minPoolSize">1</property>
+      <property name="maxPoolSize">5</property>
+      <property name="maxStatementsPerConnection">50</property>
+    </user-overrides>
+  </named-config>
+</c3p0-config>
+	</div>
+	<p>
+	  To use a named configuration, you specify the config name when creating your DataSource. For example, using
+	  <a href="apidocs/com/mchange/v2/c3p0/ComboPooledDataSource.html"><tt>ComboPooledDataSource</tt></a>:
+	</p>
+	<div class="example">
+ComboPooledDataSource cpds = new ComboPooledDataSource("intergalactoApp");  
+	</div>
+	<p>
+	  Or using the
+	  <a href="apidocs/com/mchange/v2/c3p0/DataSources.html"><tt>DataSources</tt></a> factory class:
+	</p>
+	<div class="example">
+DataSource ds_pooled = DataSources.pooledDataSource( ds_unpooled, "intergalactoApp" );
+	</div>
+<!--	
+	<h3>
+	  <a name="programmatic_configuration">Programmatic configuration of DataSource instances</a>
+	  <span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+	</h3>
+  -->
+	<h3>
+	  <a name="configuration_precedence">Precedence of Configuration Settings</a>
+	  <span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+	</h3>
+	<p>
+	  c3p0 now permits configuration parameters to be set in a number of different ways and places. Fortunately,
+	  there is a clear order of precedence that determines which configuration will "take" in the event of conflicting
+	  settings. Conceptually, c3p0 goes down this list from top to bottom, using the first setting it finds. 
+	</p>
+	<p>
+	  Most applications will never use per-user or named configurations. For these applications, we
+	  present a simplified precedence hierarchy:
+	</p>
+	<div align="center">
+	  <ol class="precedence">
+	    <li>Configuration values programmatically set.</li>
+	    <li>System property setting of configuration value.</li>
+	    <li>Configuration values taken from the default configuration of a <tt>c3p0-config.xml</tt> file.</li>
+	    <li>Configuration values specified in a <tt>c3p0.properties</tt> file</li>
+	    <li>c3p0's hard-coded default values.</li>
+	  </ol>
+	</div>
+	<p>
+	  For applications that do use named and per-user configurations, here is the complete, normative precedence hierarchy:
+	</p>
+	<div align="center">
+	  <ol class="precedence">
+	    <li>
+	      User-specific overrides programmatically set via:
+	      <ul>
+		<li><a href="apidocs/com/mchange/v2/c3p0/ComboPooledDataSource.html#setUserOverridesAsString"><tt>ComboPooledDataSource.setUserOverridesAsString()</tt></a></li>
+		<li><a href="apidocs/com/mchange/v2/c3p0/WrapperConnectionPoolDataSource.html#setUserOverridesAsString"><tt>WrapperConnectionPoolDataSource.setUserOverridesAsString()</tt></a></li>
+	      </ul>
+	      Note that programmatically setting user-specific overrides <u>replaces</u> all user-specific configuration taken from
+	      other sources. If you want to merge programmatic changes with preconfigured overrides, you'll have to use <tt>getUserOverridesAsString()</tt>
+	      and modify the original settings before replacing.
+	    </li>
+	    <li>User-specific overrides taken from a DataSource's named configuration (specified in <tt>c3p0-config.xml</tt>)</li>
+	    <li>User-specific overrides taken from the default configuration (specified in <tt>c3p0-config.xml</tt>)</li>
+	    <li>Non-user-specific values programmatically set.</li>
+	    <li>Non-user-specific values taken from a DataSource's named configuration (specified in <tt>c3p0-config.xml</tt>)</li>
+	    <li>System property setting of configuration value.</li>
+	    <li>Non-user-specific values taken from the default configuration (specified in <tt>c3p0-config.xml</tt>)</li>
+	    <li>Values specified in a <tt>c3p0.properties</tt> file</li>
+	    <li>c3p0's hard-coded default values.</li>
+	  </ol>
+	</div>
+      </div>
+      <h2>
+	<a name="hibernate-specific">Appendix C: Hibernate-specific notes</a>
+	<span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span>
+      </h2>
+      <div class="sectiontext">
+      <p>
+	Hibernate's C3P0ConnectionProvider explicitly sets 7 c3p0 configuration properties, based on your hibernate
+	configuration, <b>overriding</b> any configuration you may have set in a <tt>c3p0.properties</tt> file. If you
+	are using Hibernate's C3P0ConnectionProvider you <b>must</b> set the following
+	properties in your hibernate configuration, using hibernate-specific configuration keys. All other properties
+	must be defined as usual in a <tt>c3p0.properties</tt> file. This is confusing, and will hopefully be simplified
+	some time in the future, but for now...
+      </p>
+      <p>
+	The following properties must be set in your hibernate configuration:
+      </p>
+      <div class="hibernate_props">
+	<table class="hibernate_props">
+	  <tr>
+	    <th>c3p0-native property name</th><th>hibernate configuration key</th>
+	  </tr>
+	  <tr>
+	    <td>c3p0.acquireIncrement</td><td>hibernate.c3p0.acquire_increment</td>
+	  </tr>
+	  <tr>
+	    <td>c3p0.idleConnectionTestPeriod</td><td>hibernate.c3p0.idle_test_period</td>
+	  </tr>
+	  <tr>
+	    <td>c3p0.initialPoolSize</td><td><span class="hibparam_comment">not available -- uses minimum size</span></td>
+	  </tr>
+	  <tr>
+	    <td>c3p0.maxIdleTime</td><td>hibernate.c3p0.timeout</td>
+	  </tr>
+	  <tr>
+	    <td>c3p0.maxPoolSize</td><td>hibernate.c3p0.max_size</td>
+	  </tr>
+	  <tr>
+	    <td>c3p0.maxStatements</td><td>hibernate.c3p0.max_statements</td>
+	  </tr>
+	  <tr>
+	    <td>c3p0.minPoolSize</td><td>hibernate.c3p0.min_size</td>
+	  </tr>
+	  <tr>
+	    <td>c3p0.testConnectionsOnCheckout </td><td>hibernate.c3p0.validate <span class="hibparam_comment">hibernate 2.x only!</span></td>
+	  </tr>
+	</table>
+      </div>
+      <p>
+	Remember -- these, and <i>only these</i>, properties must be defined in your hibernate configuration, or else
+	they will be set to hibernate-specified defaults. All other configuration properties that you wish to set
+	should be defined in a <tt>c3p0.properties</tt>	file. (See <a href="#c3p0_properties">"Overriding c3p0 defaults via c3p0.properties"</a>.)
+      </p>
+    </div>
+    <hr/>
+    <h2><a name="tomcat-specific">Appendix D: Configuring c3p0 DataSources in Tomcat</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	You can easily configure Apache's Tomcat web application server to use c3p0 pooled DataSources. 
+	Below is a Tomcat 5.0  sample config to get you started. It's a fragment of Tomcat's <tt>conf/server.xml</tt> file,
+	which should be modified to suit and placed inside a <tt><Context></tt> element.
+      </p>
+      <div class="example">
+<Resource name="jdbc/pooledDS" auth="Container" type="com.mchange.v2.c3p0.ComboPooledDataSource" />
+<ResourceParams name="jdbc/pooledDS">
+  <parameter>
+    <name>factory</name>
+    <value>org.apache.naming.factory.BeanFactory</value>
+  </parameter>
+  <parameter>
+    <name>driverClass</name>
+    <value>org.postgresql.Driver</value>
+  </parameter>
+  <parameter>
+    <name>jdbcUrl</name>
+    <value>jdbc:postgresql://localhost/c3p0-test</value>
+  </parameter>
+  <parameter>
+    <name>user</name>
+    <value>swaldman</value>
+  </parameter>
+  <parameter>
+    <name>password</name>
+    <value>test</value>
+  </parameter>
+  <parameter>
+    <name>minPoolSize</name>
+    <value>5</value>
+  </parameter>
+  <parameter>
+    <name>maxPoolSize</name>
+    <value>15</value>
+  </parameter>
+  <parameter>
+    <name>acquireIncrement</name>
+    <value>5</value>
+  </parameter>
+</ResourceParams>
+      </div>
+      <p>For Tomcat 5.5, try something like the following (thanks to Carl F. Hall for the sample!):</p>
+      <div class="example">
+	<Resource auth="Container"
+	          description="DB Connection"
+		  driverClass="com.mysql.jdbc.Driver"
+		  maxPoolSize="4"
+		  minPoolSize="2"
+		  acquireIncrement="1"
+		  name="jdbc/TestDB"
+		  user="test"
+		  password="ready2go"
+		  factory="org.apache.naming.factory.BeanFactory"
+		  type="com.mchange.v2.c3p0.ComboPooledDataSource"
+		  jdbcUrl="jdbc:mysql://localhost:3306/test?autoReconnect=true" />
+      </div>
+      <p>
+	The rest is standard J2EE stuff: You'll need to declare your DataSource reference in your <tt>web.xml</tt>
+	file:
+      </p>
+	
+      <div class="example">
+<resource-ref>
+  <res-ref-name>jdbc/pooledDS</res-ref-name>
+  <res-type>javax.sql.DataSource</res-type>
+  <res-auth>Container</res-auth>
+</resource-ref>
+      </div>
+      <p>      
+	And you can access your DataSource from code within your web application like this:
+      </p>
+      <div class="example">
+InitialContext ic = new InitialContext();
+DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/pooledDS");
+      </div>
+      <p>
+	That's it!
+      </p>
+    </div>
+    <hr/>
+    <h2><a name="jboss-specific">Appendix E: JBoss-specific notes</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	To use c3p0 with JBoss:
+	<ol>
+	  <li>
+	    Place c3p0's jar file in the <tt>lib</tt> directory of your 
+	    jboss server instance (e.g. <tt>${JBOSS_HOME}/server/default/lib</tt>)
+	  </li>
+	  <li>
+	    Modify the file below, and save it as <tt>c3p0-service.xml</tt> in the 
+	    <tt>deploy</tt> directory of your jboss server (e.g. <tt>${JBOSS_HOME}/server/default/deploy</tt>).
+	    Note that parameters must be capitalized in this file, but otherwise they are defined as described
+	    above.
+	  </li>
+	</ol>
+    <p>
+        <div id="showDeprecatedJbossConfigName" style="display: block">
+        <b>Note:</b> <a href="#" onClick="return toggleDisplay('showDeprecatedJbossConfigName', 'DeprecatedJbossConfigName');">
+        Users of c3p0 jboss support prior to c3p0-0.9.1 please click here!</a>
+        </div>
+	</p>
+	<div class="deprecated" id="DeprecatedJbossConfigName">
+	<p>
+	   <b>Please note: As of c3p0-0.9.1, the class name of the jboss configuration mbean has changed
+	      to <tt>com.mchange.v2.c3p0.jboss.C3P0PooledDataSource</tt> (from <tt>com.mchange.v2.c3p0.mbean.C3P0PooledDataSource</tt>),
+	      in order to distinguish what is really jboss-specific functionality from c3p0's more general JMX
+	      support.</b> 
+	</p>
+	<p>
+	      The old jboss config mbeans are deprecated, but will still work. However, support for new configuration
+	      parameters will only be added under the new name. Updating requires a one-word change to your <tt>c3p0-service.xml</tt>,
+	      change "mbean" to "jboss" where your old file says 'code="com.mchange.v2.c3p0.mbean.C3P0PooledDataSource"'. Just do it! 
+	</p>
+	<p>
+		<a id="hideDataSourcesWithPoolConfig" 
+	   	href="#" 
+	   	onClick="return toggleDisplay('showDeprecatedJbossConfigName', 'DeprecatedJbossConfigName');"
+	   	>Hide box.</a>
+	 <p>
+      </div>
+	
+	</div>
+      <div class="example">
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE server>
+
+<server>
+
+   <mbean code="com.mchange.v2.c3p0.jboss.C3P0PooledDataSource"
+          name="jboss:service=C3P0PooledDataSource">
+     
+      <attribute name="JndiName">java:PooledDS</attribute>
+      <attribute name="JdbcUrl">jdbc:postgresql://localhost/c3p0-test</attribute>
+      <attribute name="DriverClass">org.postgresql.Driver</attribute>
+      <attribute name="User">swaldman</attribute>
+      <attribute name="Password">test</attribute>
+
+      <!-- Uncomment and set any of the optional parameters below -->
+      <!-- See c3p0's docs for more info.                         -->
+
+      <!-- <attribute name="AcquireIncrement">3</attribute>                         -->
+      <!-- <attribute name="AcquireRetryAttempts">30</attribute>                    -->
+      <!-- <attribute name="AcquireRetryDelay">1000</attribute>                     -->
+      <!-- <attribute name="AutoCommitOnClose">false</attribute>                    -->
+      <!-- <attribute name="AutomaticTestTable"></attribute>                        -->
+      <!-- <attribute name="BreakAfterAcquireFailure">false</attribute>             -->
+      <!-- <attribute name="CheckoutTimeout">0</attribute>                          -->
+      <!-- <attribute name="ConnectionCustomizerClassName"></attribute>             -->
+      <!-- <attribute name="ConnectionTesterClassName"></attribute>                 -->
+      <!-- <attribute name="Description">A pooled c3p0 DataSource</attribute>       -->
+      <!-- <attribute name="DebugUnreturnedConnectionStackTraces">false</attribute> -->
+      <!-- <attribute name="FactoryClassLocation"></attribute>                      -->
+      <!-- <attribute name="ForceIgnoreUnresolvedTransactions">false</attribute>    -->
+      <!-- <attribute name="IdleConnectionTestPeriod">0</attribute>                 -->
+      <!-- <attribute name="InitialPoolSize">3</attribute>                          -->
+      <!-- <attribute name="MaxAdministrativeTaskTime">0</attribute>                -->
+      <!-- <attribute name="MaxConnectionAge">0</attribute>                         -->
+      <!-- <attribute name="MaxIdleTime">0</attribute>                              -->
+      <!-- <attribute name="MaxIdleTimeExcessConnections">0</attribute>             -->
+      <!-- <attribute name="MaxPoolSize">15</attribute>                             -->
+      <!-- <attribute name="MaxStatements">0</attribute>                            -->
+      <!-- <attribute name="MaxStatementsPerConnection">0</attribute>               -->
+      <!-- <attribute name="MinPoolSize">0</attribute>                              -->
+      <!-- <attribute name="NumHelperThreads">3</attribute>                         -->
+      <!-- <attribute name="PreferredTestQuery"></attribute>                        -->
+      <!-- <attribute name="TestConnectionOnCheckin">false</attribute>              -->
+      <!-- <attribute name="TestConnectionOnCheckout">false</attribute>             -->
+      <!-- <attribute name="UnreturnedConnectionTimeout">0</attribute>              -->
+      <!-- <attribute name="UsesTraditionalReflectiveProxies">false</attribute>     -->
+
+
+      <depends>jboss:service=Naming</depends>
+   </mbean>
+
+</server>
+      </div>
+      </p>
+    </div>
+    <hr/>
+    <h2><a name="oracle-specific">Appendix F: Oracle-specific API: createTemporaryBLOB() and createTemporaryCLOB()</a><span class="toplink"><a href="#contents"><img src="arrow_sm.png" width="20" alt="Go To Top"/></a></span></h2>
+    <div class="sectiontext">
+      <p>
+	The Oracle thin JDBC driver provides a non-standard API for creating temporary BLOBs and CLOBs that
+	requires users to call methods on the raw, Oracle-specific Connection implementation. Advanced users
+	might use the <a href="#raw_connection_ops">raw connection operations</a> described above to access this
+	functionality, but a convenience class is available in a separate jar file (<tt>c3p0-oracle-thin-extras- at c3p0.version@.jar</tt>)
+	for easier access to this functionality. Please see the 
+	<a href="apidocs-oracle-thin/index.html">API docs for <tt>com.mchange.v2.c3p0.dbms.OracleUtils</tt></a>
+	for details.
+      </p>
+    </div>
+    <hr/>
+    <a href="#contents">Back to Contents</a>
+  </body>
+</html>
diff --git a/src/docweb/docwebapp/WEB-INF/jboss-web.xml b/src/docweb/docwebapp/WEB-INF/jboss-web.xml
new file mode 100644
index 0000000..89f7d68
--- /dev/null
+++ b/src/docweb/docwebapp/WEB-INF/jboss-web.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='UTF-8' ?>
+
+<!DOCTYPE jboss-web
+    PUBLIC "-//JBoss//DTD Web Application 2.3//EN"
+   "http://www.jboss.org/j2ee/dtd/jboss-web_3_0.dtd">
+
+<jboss-web>
+    <virtual-host>@virtual.host@</virtual-host>
+</jboss-web>
diff --git a/src/docweb/docwebapp/WEB-INF/web.xml b/src/docweb/docwebapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c76f1a4
--- /dev/null
+++ b/src/docweb/docwebapp/WEB-INF/web.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!DOCTYPE web-app
+    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+    "http://java.sun.com/dtd/web-app_2_3.dtd">
+
+<web-app />
diff --git a/src/docweb/docwebear/META-INF/application.xml b/src/docweb/docwebear/META-INF/application.xml
new file mode 100644
index 0000000..badffa2
--- /dev/null
+++ b/src/docweb/docwebear/META-INF/application.xml
@@ -0,0 +1,15 @@
+<!DOCTYPE application PUBLIC
+	    "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN"
+	    "http://java.sun.com/dtd/application_1_3.dtd">
+
+
+<application>
+   <display-name>www.mchange.com</display-name>
+   <module>
+       <web>
+          <web-uri>@web.uri@</web-uri>
+          <context-root>@context.root@</context-root>
+       </web>
+   </module>
+</application>
+
diff --git a/src/resources/com/mchange/v2/cfg/junit/a.properties b/src/resources/com/mchange/v2/cfg/junit/a.properties
new file mode 100644
index 0000000..604534c
--- /dev/null
+++ b/src/resources/com/mchange/v2/cfg/junit/a.properties
@@ -0,0 +1 @@
+user.home=/a/home
diff --git a/src/resources/com/mchange/v2/cfg/junit/b.properties b/src/resources/com/mchange/v2/cfg/junit/b.properties
new file mode 100644
index 0000000..ad69d7f
--- /dev/null
+++ b/src/resources/com/mchange/v2/cfg/junit/b.properties
@@ -0,0 +1 @@
+user.home=/b/home
diff --git a/src/resources/com/mchange/v2/cfg/vmConfigResourcePaths.txt b/src/resources/com/mchange/v2/cfg/vmConfigResourcePaths.txt
new file mode 100644
index 0000000..2de37a2
--- /dev/null
+++ b/src/resources/com/mchange/v2/cfg/vmConfigResourcePaths.txt
@@ -0,0 +1,11 @@
+#
+# note that later files "shadow" earlier ones, and that
+# the name '/' is reserved as a special token for
+# System properties.
+#
+
+/mchange-commons.properties
+/mchange-log.properties
+/c3p0.properties
+/
+
diff --git a/src/resources/com/mchange/v2/log/default-mchange-log.properties b/src/resources/com/mchange/v2/log/default-mchange-log.properties
new file mode 100644
index 0000000..8013b18
--- /dev/null
+++ b/src/resources/com/mchange/v2/log/default-mchange-log.properties
@@ -0,0 +1,3 @@
+com.mchange.v2.log.MLog=com.mchange.v2.log.log4j.Log4jMLog,com.mchange.v2.log.jdk14logging.Jdk14MLog
+
+
diff --git a/test-properties/c3p0-config.xml b/test-properties/c3p0-config.xml
new file mode 100644
index 0000000..586b8ed
--- /dev/null
+++ b/test-properties/c3p0-config.xml
@@ -0,0 +1,38 @@
+<c3p0-config>
+  <default-config>
+    <!-- <property name="automaticTestTable">con_test</property> -->
+    <!-- <property name="checkoutTimeout">30000</property> -->
+    <!-- <property name="idleConnectionTestPeriod">30</property> -->
+    <!-- <property name="initialPoolSize">10</property> -->
+    <!-- <property name="maxIdleTime">30</property> -->
+    <!-- <property name="maxIdleTimeExcessConnections">10</property> -->
+    <!-- <property name="maxConnectionAge">60</property> -->
+    <!-- <property name="propertyCycle">1</property> -->
+    <!-- <property name="maxPoolSize">25</property> -->
+    <!-- <property name="minPoolSize">5</property> -->
+    <!-- <property name="maxStatements">0</property> -->
+    <!-- <property name="maxStatementsPerConnection">5</property> -->
+    <!-- <property name="maxAdministrativeTaskTime">4</property> -->
+    <!-- <property name="connectionCustomizerClassName">com.mchange.v2.c3p0.test.TestConnectionCustomizer</property> -->
+    <!-- <property name="unreturnedConnectionTimeout">15</property> -->
+    <!-- <property name="debugUnreturnedConnectionStackTraces">true</property> -->
+
+    <!--
+    <user-overrides user="swaldman">
+      <property name="debugUnreturnedConnectionStackTraces">true</property>
+    </user-overrides>
+    -->
+
+  </default-config>
+
+<!--
+  <named-config name="dumbTestConfig">
+    <property name="maxStatements">200</property>
+    <property name="jdbcUrl">jdbc:test</property>
+    <user-overrides user="poop">
+      <property name="maxStatements">300</property>
+    </user-overrides>
+   </named-config>
+-->
+
+</c3p0-config>
diff --git a/test-properties/c3p0.properties b/test-properties/c3p0.properties
new file mode 100644
index 0000000..5161eff
--- /dev/null
+++ b/test-properties/c3p0.properties
@@ -0,0 +1,54 @@
+#
+# This file is detritus from various testing attempts
+# the values below may change, and often do not represent
+# reasonable values for the parameters.
+#
+
+#c3p0.testConnectionOnCheckout=true
+#c3p0.testConnectionOnCheckin=true
+#c3p0.minPoolSize=3
+#c3p0.maxPoolSize=20
+#c3p0.checkoutTimeout=2000
+#c3p0.idleConnectionTestPeriod=5
+#c3p0.maxConnectionAge=10
+#c3p0.maxIdleTime=2
+#c3p0.maxIdleTimeExcessConnections=1
+#c3p0.propertyCycle=1
+#c3p0.numHelperThreads=10
+#c3p0.unreturnedConnectionTimeout=15
+#c3p0.debugUnreturnedConnectionStackTraces=true
+#c3p0.maxStatements=30
+#c3p0.maxStatementsPerConnection=5
+#c3p0.maxAdministrativeTaskTime=3
+#c3p0.preferredTestQuery=SELECT 1
+#c3p0.preferredTestQuery=SELECT a FROM emptyyukyuk WHERE a = 5
+#c3p0.preferredTestQuery=SELECT a FROM testpbds WHERE a = 5
+#c3p0.usesTraditionalReflectiveProxies=true
+#c3p0.automaticTestTable=PoopyTestTable
+#c3p0.acquireIncrement=4
+#c3p0.acquireRetryDelay=1000
+#c3p0.acquireRetryAttempts=60
+#c3p0.connectionTesterClassName=com.mchange.v2.c3p0.test.AlwaysFailConnectionTester
+#c3p0.initialPoolSize=10
+
+c3p0.jdbcUrl=jdbc:postgresql://localhost/c3p0-test
+c3p0.driverClass=org.postgresql.Driver
+c3p0.user=swaldman
+c3p0.password=test
+#c3p0.user=poop
+#c3p0.password=scoop
+
+#com.mchange.v2.log.MLog=com.mchange.v2.log.log4j.Log4jMLog
+#com.mchange.v2.log.MLog=com.mchange.v2.log.jdk14logging.Jdk14MLog
+#com.mchange.v2.log.MLog=com.mchange.v2.log.FallbackMLog
+#com.mchange.v2.log.NameTransformer=com.mchange.v2.log.PackageNames
+#com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL=ALL
+
+
+#com.mchange.v2.c3p0.VMID=poop
+
+
+
+
+
+
diff --git a/test-properties/log4j.properties b/test-properties/log4j.properties
new file mode 100644
index 0000000..0198201
--- /dev/null
+++ b/test-properties/log4j.properties
@@ -0,0 +1,17 @@
+
+#log4j.rootLogger=ALL,A1
+log4j.rootLogger=INFO,A1
+#log4j.logger.com.mchange.v2.resourcepool=ALL,A1
+#log4j.logger.com.mchange.v2.c3p0.stmt=ALL
+#log4j.logger.com.mchange.v2.c3p0.impl=ALL
+#log4j.logger.com.mchange.v2.c3p0.management=ALL
+#log4j.logger.com.mchange.v2.resourcepool=ALL
+
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+log4j.appender.A1.layout=org.apache.log4j.SimpleLayout
+#log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+
+#log4j.appender.A1.layout.ConversionPattern=%r [%t] %-5p %c %x - %m\n
+#log4j.appender.A1.layout.ConversionPattern=%d %-5p %c [%t] %C %M --> %m%n
+#log4j.appender.A1.layout.ConversionPattern=%-5p %c %C.%M() --> %m%n
diff --git a/test-properties/logging.properties b/test-properties/logging.properties
new file mode 100644
index 0000000..42f01be
--- /dev/null
+++ b/test-properties/logging.properties
@@ -0,0 +1,66 @@
+############################################################
+#  	Default Logging Configuration File
+#
+# For example java -Djava.util.logging.config.file=myfile
+############################################################
+
+############################################################
+#  	Global properties
+############################################################
+
+# "handlers" specifies a comma separated list of log Handler 
+# classes.  These handlers will be installed during VM startup.
+# Note that these classes must be on the system classpath.
+# By default we only configure a ConsoleHandler, which will only
+# show messages at the INFO and above levels.
+handlers=java.util.logging.ConsoleHandler
+
+# To also add the FileHandler, use the following line instead.
+#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
+
+# Default global logging level.
+# This specifies which kinds of events are logged across
+# all loggers.  For any given facility this global level
+# can be overriden by a facility specific level
+# Note that the ConsoleHandler also has a separate level
+# setting to limit messages printed to the console.
+
+#.level=ALL
+#.level=FINER
+#.level=OFF
+.level=INFO
+
+############################################################
+# Handler specific properties.
+# Describes specific configuration info for Handlers.
+############################################################
+
+# default file output is in user's home directory.
+java.util.logging.FileHandler.pattern=%h/java%u.log
+java.util.logging.FileHandler.limit=50000
+java.util.logging.FileHandler.count=1
+java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter
+
+# Limit the message that are printed on the console to INFO and above.
+java.util.logging.ConsoleHandler.level=ALL
+java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+
+
+############################################################
+# Facility specific properties.
+# Provides extra control for each logger.
+############################################################
+
+# For example, set the com.xyz.foo logger to only log SEVERE
+# messages:
+
+#com.xyz.foo.level = SEVERE
+#com.mchange.v2.resourcepool.level=INFO
+#com.mchange.v2.resourcepool.level=FINER
+com.mchange.v2.resourcepool.level=ALL
+#com.mchange.v2.c3p0.impl.level=ALL
+#com.mchange.level=FINE
+#com.mchange.level=ALL
+#com.mchange.v2.c3p0.impl.level=ALL
+#com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.level=ALL
+
diff --git a/version.properties b/version.properties
new file mode 100644
index 0000000..5b84063
--- /dev/null
+++ b/version.properties
@@ -0,0 +1,3 @@
+#autogenerated -- do not edit!
+#Mon May 21 15:04:56 EDT 2007
+c3p0.version=0.9.1.2

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/c3p0.git



More information about the pkg-java-commits mailing list