diff --git a/build.gradle.kts b/build.gradle.kts index e04db06..e9e53b9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.* import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.plugin.xomrk @@ -27,12 +28,26 @@ xomrk { optInContracts() explicitApi() - jvm() + jvm { + withJava() + + /*compilations.configureEach { + compileJavaTaskProvider?.configure { + targetCompatibility = "1.8" + } + compileTaskProvider.configure { + compilerOptions { jvmTarget = JvmTarget.JVM_1_8 } + } + }*/ + } sourceSets { jvmMain { dependencies { + compileOnly("org.jetbrains:annotations:26.0.2") compileOnly("com.intellij:annotations:9.0.4") + compileOnly("com.intellij:annotations:9.0.4") + implementation("it.unimi.dsi:fastutil:6.3") implementation("ru.landgrafhomyak.utility:highlevel-try-finally:0.4") implementation("ru.landgrafhomyak.utility:reference-counter:0.1") } diff --git a/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.java b/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.java new file mode 100644 index 0000000..e36bd4f --- /dev/null +++ b/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.java @@ -0,0 +1,118 @@ +package ru.langrafhomyak.db.jdbc_resources_manager; + +import org.intellij.lang.annotations.Language; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.WeakReference; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import static java.lang.System.identityHashCode; + +public class PreparedStatementsCompilationPhantomCache /*implements ReadOnlyProperty*/ { + @NotNull + private final String _statementSource; + @NotNull + private final _SynchronizedEmbedKeyHashMap _map; + + public PreparedStatementsCompilationPhantomCache(@NotNull @Language("SQL") String statementSource) { + this._statementSource = statementSource; + this._map = new _SynchronizedEmbedKeyHashMap<>(); + } + + @NotNull + PreparedStatement preparedStatementForConnection(@NotNull Connection connection) throws SQLException { + final int key = identityHashCode(connection); + ConnectionsMapNode node = this._map.get(key, connection); + if (node == null) { + /* + Assuming that user will clever enough to not use the same connection in different threads + and there wouldn't be a race condition for adding instance of this connection to map. + Anyway, technically there is no error if the same connection is added twice. + */ + node = new ConnectionsMapNode(this, connection); + this._map.add(node); + } + return node.createWrapper(); + } + + private static class ConnectionsMapNode extends _SynchronizedEmbedKeyHashMap.Node { + + @NotNull + private final BoundPhantomReference _connectionRef; + @NotNull + private final WeakReference _statement; + @Nullable + private PreparedStatement _wrapper; + @NotNull + private final Object _wrapperSync; + + public ConnectionsMapNode(@NotNull PreparedStatementsCompilationPhantomCache owner, @NotNull Connection connection) throws SQLException { + super(identityHashCode(connection)); + this._connectionRef = owner.new BoundPhantomReference(connection, this); + this._statement = new WeakReference<>(connection.prepareStatement(owner._statementSource)); + this._wrapper = null; + this._wrapperSync = new Object(); + } + + @Override + protected boolean _checkKeyReference(@NotNull Object ref) { + return this._connectionRef.refersTo(ref); + } + + @NotNull + public PreparedStatement createWrapper() { + synchronized (this._wrapperSync) { + if (this._wrapper != null) + throw new IllegalStateException("Requested prepared statement already used by this connection (or not closed after using)"); + PreparedStatement orig = this._statement.get(); + if (orig == null) + throw new RuntimeException(""); + PreparedStatement wrapper = new BoundPreparedStatementWrapper(orig, this); + this._wrapper = wrapper; + return wrapper; + } + } + + public void unbindWrapper() { + synchronized (this._wrapperSync) { + this._wrapper = null; + } + } + } + + private class BoundPhantomReference extends _PhantomCacheCleanup.CleanablePhantomReference { + @NotNull + private final ConnectionsMapNode descriptor; + + public BoundPhantomReference(@NotNull Connection ref, @NotNull ConnectionsMapNode descriptor) { + super(ref); + this.descriptor = descriptor; + } + + @Override + public void cleanup() { + PreparedStatementsCompilationPhantomCache.this._map.remove(this.descriptor); + } + } + + private static class BoundPreparedStatementWrapper extends PreparedStatementWrapper { + @NotNull + private final ConnectionsMapNode _node; + + BoundPreparedStatementWrapper(@NotNull PreparedStatement orig, @NotNull ConnectionsMapNode node) { + super(orig); + this._node = node; + } + + @Override + protected void _onClose() throws SQLException { + this._orig.clearParameters(); + this._orig.clearBatch(); + this._orig.clearWarnings(); + this._node.unbindWrapper(); + } + } +} diff --git a/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/_SynchronizedEmbedKeyHashMap.java b/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/_SynchronizedEmbedKeyHashMap.java new file mode 100644 index 0000000..9de6c6b --- /dev/null +++ b/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/_SynchronizedEmbedKeyHashMap.java @@ -0,0 +1,73 @@ +package ru.langrafhomyak.db.jdbc_resources_manager; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/* package */ class _SynchronizedEmbedKeyHashMap> { + public static abstract class Node> { + public final int keyHashCode; + @Nullable + /*private*/ T _nextNode; + + public Node(int keyHashCode) { + this.keyHashCode = keyHashCode; + } + + protected abstract boolean _checkKeyReference(@NotNull Object ref); + } + + + @NotNull + private final Int2ObjectOpenHashMap _impl; + @NotNull + private final Object _sync; + + public _SynchronizedEmbedKeyHashMap() { + this._impl = new Int2ObjectOpenHashMap<>(); + this._sync = new Object(); + } + + @Nullable + public T get(int keyHashCode, @NotNull Object ref) { + synchronized (this._sync) { + T p = this._impl.get(keyHashCode); + while (p != null) { + if (p._checkKeyReference(ref)) + return p; + p = p._nextNode; + } + } + return null; + } + + public void add(@NotNull T node) { + synchronized (this._sync) { + node._nextNode = this._impl.put(node.keyHashCode, node); + } + } + + public void remove(@NotNull T node) { + synchronized (this._sync) { + REMOVED: + { + T p = this._impl.get(node.keyHashCode); + if (p == node) { + this._impl.put(node.keyHashCode, p._nextNode); + break REMOVED; + } + + while (p != null) { + T n = p._nextNode; + if (n == node) { + p._nextNode = n._nextNode; + break REMOVED; + } + p = n; + } + throw new IllegalArgumentException("Node not found"); + } + this._impl.trim(); + } + } +} diff --git a/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementWrapper.kt b/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementWrapper.kt index 4c7a5c3..a222629 100644 --- a/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementWrapper.kt +++ b/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementWrapper.kt @@ -23,15 +23,17 @@ import java.sql.Statement import java.sql.Time import java.sql.Timestamp import java.util.Calendar +import kotlin.jvm.Throws import ru.landrafhomyak.utility.reference_counter.CloseableReferenceCounter @Suppress("UsePropertyAccessSyntax") -internal abstract class PreparedStatementWrapper(protected val _orig: PreparedStatement) : PreparedStatement { +internal abstract class PreparedStatementWrapper(@JvmField protected val _orig: PreparedStatement) : PreparedStatement { private val _refcnt = CloseableReferenceCounter("This prepared statement was returned to pool to be used in future") private var _currentQuery: ResultSetWrapper? = null private var _closeOnCompletion: Boolean = false private var _isClosed = false + @Throws(SQLException::class) protected abstract fun _onClose() override fun close() { @@ -312,7 +314,7 @@ internal abstract class PreparedStatementWrapper(protected val _orig: PreparedSt override fun getResultSetType() = this._refcnt.withRef(this._orig::getFetchSize) - override fun addBatch(sql: String?) = + override fun addBatch(sql: String?): Unit = throw SQLException("addBatch(String) not allowed on PreparedStatement") override fun clearBatch() = diff --git a/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.kt b/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.kt deleted file mode 100644 index 0741957..0000000 --- a/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.kt +++ /dev/null @@ -1,132 +0,0 @@ -package ru.langrafhomyak.db.jdbc_resources_manager - -import java.sql.Connection -import java.sql.PreparedStatement -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty -import org.intellij.lang.annotations.Language - -public class PreparedStatementsCompilationPhantomCache( - @Language("SQL") - private val _statementSource: String -) : ReadOnlyProperty { - private val _mapSync = ReentrantLock() - private val _map = arrayOfNulls(7) - private var _connectionsCount = 0 - - override fun getValue(thisRef: Any?, property: KProperty<*>): Accessor = - this.Accessor() - - public inner class Accessor { - public operator fun invoke(connection: Connection): PreparedStatement = - this@PreparedStatementsCompilationPhantomCache.getForConnection(connection) - } - - private class ConnectionsMapNode( - owner: PreparedStatementsCompilationPhantomCache, - connection: Connection, - @JvmField - var nextNode: ConnectionsMapNode?, - @JvmField - val connectionHashCode: Int - ) { - @JvmField - val connection = owner.BoundPhantomReference(connection, this) - - @JvmField - var statement: PreparedStatement? = null - - @JvmField - val lock = ReentrantLock() - - @JvmField - var wrapper: BoundPreparedStatementWrapper? = null - } - - private fun _resizeIfNeeded() { - if (this._connectionsCount / 0.75 > this._map.size) { - val newMap = arrayOfNulls(this._map.size * 2) - for (s in this._map) { - var p = s - while (p != null) { - val i = Integer.remainderUnsigned(p.connectionHashCode, this._map.size) - val n = p.nextNode - p.nextNode = newMap[i] - newMap[i] = p - p = n - } - } - } - } - - private fun _getNode(connection: Connection): ConnectionsMapNode { - val newNode = this._mapSync.withLock { - val connectionHashCode = connection.hashCode() - val index = Integer.remainderUnsigned(connectionHashCode, this._map.size) - var p = this._map[index] - while (p != null) { - if (p.connection.refersTo(connection)) - return@_getNode p - p = p.nextNode - } - val node = ConnectionsMapNode(this, connection, this._map[index], connectionHashCode) - this._map[index] = node - this._connectionsCount++ - this._resizeIfNeeded() - return@withLock node - } - - newNode.statement = connection.prepareStatement(this._statementSource) - return newNode - } - - public fun getForConnection(connection: Connection): PreparedStatement { - val node = this._getNode(connection) - val wrapper: PreparedStatement - node.lock.withLock { - if (node.wrapper != null) - throw IllegalStateException("Requested prepared statement already used by this connection (or not closed after using)") - wrapper = BoundPreparedStatementWrapper(node.statement!!, node) - node.wrapper = wrapper - } - return wrapper - } - - - private inner class BoundPhantomReference( - ref: Connection, - private val descriptor: ConnectionsMapNode - ) : _PhantomCacheCleanup.CleanablePhantomReference(ref) { - override fun cleanup() { - this@PreparedStatementsCompilationPhantomCache._mapSync.withLock { - val i = Integer.remainderUnsigned(this.descriptor.connectionHashCode, this@PreparedStatementsCompilationPhantomCache._map.size) - var r = this@PreparedStatementsCompilationPhantomCache._map[i] - if (r === this.descriptor) { - this@PreparedStatementsCompilationPhantomCache._map[i] = r.nextNode - } - while (r != null) { - val n = r.nextNode - if (n === this.descriptor) { - r.nextNode = n.nextNode - break - } - r = n - } - } - } - } - - private class BoundPreparedStatementWrapper(orig: PreparedStatement, private val _node: ConnectionsMapNode) : PreparedStatementWrapper(orig) { - override fun _onClose() { - this._orig.clearParameters() - this._orig.clearBatch() - this._orig.clearWarnings() - this._orig.close() - this._node.lock.withLock { - this._node.wrapper = null - } - } - } -} \ No newline at end of file