package xyz.nulldev.androidcompat.db import java.io.InputStream import java.io.Reader 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.NClob import java.sql.Ref import java.sql.ResultSet import java.sql.ResultSetMetaData import java.sql.RowId import java.sql.SQLXML import java.sql.Time import java.sql.Timestamp import java.util.Calendar @Suppress("UNCHECKED_CAST") class ScrollableResultSet( val parent: ResultSet, ) : ResultSet by parent { private val cachedContent = mutableListOf() private val columnCache = mutableMapOf() private var lastReturnWasNull = false private var cursor = 0 var resultSetLength = 0 val parentMetadata = parent.metaData val columnCount = parentMetadata.columnCount val columnLabels = (1..columnCount) .map { parentMetadata.getColumnLabel(it) }.toTypedArray() init { val columnCount = columnCount // TODO // Profiling reveals that this is a bottleneck (average ms for this call is: 48ms) // How can we optimize this? // We need to fill the cache as the set is loaded // Fill cache while (parent.next()) { cachedContent += ResultSetEntry().apply { for (i in 1..columnCount) { data += parent.getObject(i) } } resultSetLength++ } } private fun notImplemented(): Nothing = throw UnsupportedOperationException("This class currently does not support this operation!") private fun cursorValid(): Boolean = isAfterLast || isBeforeFirst private fun internalMove(row: Int) { if (cursor < 0) { cursor = 0 } else if (cursor > resultSetLength + 1) { cursor = resultSetLength + 1 } else { cursor = row } } private fun obj(column: Int): Any? { val obj = cachedContent[cursor - 1].data[column - 1] lastReturnWasNull = obj == null return obj } private fun obj(column: String?): Any? = obj(cachedFindColumn(column)) private fun cachedFindColumn(column: String?) = columnCache.getOrPut(column!!, { findColumn(column) }) override fun getNClob(columnIndex: Int): NClob = obj(columnIndex) as NClob override fun getNClob(columnLabel: String?): NClob = obj(columnLabel) as NClob override fun updateNString( columnIndex: Int, nString: String?, ) { notImplemented() } override fun updateNString( columnLabel: String?, nString: String?, ) { notImplemented() } override fun updateBinaryStream( columnIndex: Int, x: InputStream?, length: Int, ) { notImplemented() } override fun updateBinaryStream( columnLabel: String?, x: InputStream?, length: Int, ) { notImplemented() } override fun updateBinaryStream( columnIndex: Int, x: InputStream?, length: Long, ) { notImplemented() } override fun updateBinaryStream( columnLabel: String?, x: InputStream?, length: Long, ) { notImplemented() } override fun updateBinaryStream( columnIndex: Int, x: InputStream?, ) { notImplemented() } override fun updateBinaryStream( columnLabel: String?, x: InputStream?, ) { notImplemented() } override fun updateTimestamp( columnIndex: Int, x: Timestamp?, ) { notImplemented() } override fun updateTimestamp( columnLabel: String?, x: Timestamp?, ) { notImplemented() } override fun updateNCharacterStream( columnIndex: Int, x: Reader?, length: Long, ) { notImplemented() } override fun updateNCharacterStream( columnLabel: String?, reader: Reader?, length: Long, ) { notImplemented() } override fun updateNCharacterStream( columnIndex: Int, x: Reader?, ) { notImplemented() } override fun updateNCharacterStream( columnLabel: String?, reader: Reader?, ) { notImplemented() } override fun updateInt( columnIndex: Int, x: Int, ) { notImplemented() } override fun updateInt( columnLabel: String?, x: Int, ) { notImplemented() } override fun moveToInsertRow() { notImplemented() } override fun getDate(columnIndex: Int): Date { // TODO Maybe? notImplemented() } override fun getDate(columnLabel: String?): Date { // TODO Maybe? notImplemented() } override fun getDate( columnIndex: Int, cal: Calendar?, ): Date { // TODO Maybe? notImplemented() } override fun getDate( columnLabel: String?, cal: Calendar?, ): Date { // TODO Maybe? notImplemented() } override fun beforeFirst() { // TODO Maybe? notImplemented() } override fun updateFloat( columnIndex: Int, x: Float, ) { notImplemented() } override fun updateFloat( columnLabel: String?, x: Float, ) { notImplemented() } override fun getBoolean(columnIndex: Int): Boolean = obj(columnIndex) as Boolean override fun getBoolean(columnLabel: String?): Boolean = obj(columnLabel) as Boolean override fun isFirst(): Boolean = cursor - 1 < resultSetLength override fun getBigDecimal( columnIndex: Int, scale: Int, ): BigDecimal { // TODO Maybe? notImplemented() } override fun getBigDecimal( columnLabel: String?, scale: Int, ): BigDecimal { // TODO Maybe? notImplemented() } override fun getBigDecimal(columnIndex: Int): BigDecimal = obj(columnIndex) as BigDecimal override fun getBigDecimal(columnLabel: String?): BigDecimal = obj(columnLabel) as BigDecimal override fun updateBytes( columnIndex: Int, x: ByteArray?, ) { notImplemented() } override fun updateBytes( columnLabel: String?, x: ByteArray?, ) { notImplemented() } override fun isLast(): Boolean = cursor == resultSetLength override fun insertRow() { notImplemented() } override fun getTime(columnIndex: Int): Time { // TODO Maybe? notImplemented() } override fun getTime(columnLabel: String?): Time { // TODO Maybe? notImplemented() } override fun getTime( columnIndex: Int, cal: Calendar?, ): Time { // TODO Maybe? notImplemented() } override fun getTime( columnLabel: String?, cal: Calendar?, ): Time { // TODO Maybe? notImplemented() } override fun rowDeleted() = false override fun last(): Boolean { internalMove(resultSetLength) return cursorValid() } override fun isAfterLast(): Boolean = cursor > resultSetLength override fun relative(rows: Int): Boolean { internalMove(cursor + rows) return cursorValid() } override fun absolute(row: Int): Boolean { if (row > 0) { internalMove(row) } else { last() for (i in 1..row) { previous() } } return cursorValid() } override fun getSQLXML(columnIndex: Int): SQLXML? { // TODO Maybe? notImplemented() } override fun getSQLXML(columnLabel: String?): SQLXML? { // TODO Maybe? notImplemented() } override fun unwrap(iface: Class?): T { if (thisIsWrapperFor(iface)) { return this as T } else { return parent.unwrap(iface) } } override fun next(): Boolean { internalMove(cursor + 1) return cursorValid() } override fun getFloat(columnIndex: Int): Float = obj(columnIndex) as Float override fun getFloat(columnLabel: String?): Float = obj(columnLabel) as Float override fun wasNull() = lastReturnWasNull override fun getRow(): Int = cursor override fun first(): Boolean { internalMove(1) return cursorValid() } override fun updateAsciiStream( columnIndex: Int, x: InputStream?, length: Int, ) { notImplemented() } override fun updateAsciiStream( columnLabel: String?, x: InputStream?, length: Int, ) { notImplemented() } override fun updateAsciiStream( columnIndex: Int, x: InputStream?, length: Long, ) { notImplemented() } override fun updateAsciiStream( columnLabel: String?, x: InputStream?, length: Long, ) { notImplemented() } override fun updateAsciiStream( columnIndex: Int, x: InputStream?, ) { notImplemented() } override fun updateAsciiStream( columnLabel: String?, x: InputStream?, ) { notImplemented() } override fun getURL(columnIndex: Int): URL = obj(columnIndex) as URL override fun getURL(columnLabel: String?): URL = obj(columnLabel) as URL override fun updateShort( columnIndex: Int, x: Short, ) { notImplemented() } override fun updateShort( columnLabel: String?, x: Short, ) { notImplemented() } override fun getType() = ResultSet.TYPE_SCROLL_INSENSITIVE override fun updateNClob( columnIndex: Int, nClob: NClob?, ) { notImplemented() } override fun updateNClob( columnLabel: String?, nClob: NClob?, ) { notImplemented() } override fun updateNClob( columnIndex: Int, reader: Reader?, length: Long, ) { notImplemented() } override fun updateNClob( columnLabel: String?, reader: Reader?, length: Long, ) { notImplemented() } override fun updateNClob( columnIndex: Int, reader: Reader?, ) { notImplemented() } override fun updateNClob( columnLabel: String?, reader: Reader?, ) { notImplemented() } override fun updateRef( columnIndex: Int, x: Ref?, ) { notImplemented() } override fun updateRef( columnLabel: String?, x: Ref?, ) { notImplemented() } override fun updateObject( columnIndex: Int, x: Any?, scaleOrLength: Int, ) { notImplemented() } override fun updateObject( columnIndex: Int, x: Any?, ) { notImplemented() } override fun updateObject( columnLabel: String?, x: Any?, scaleOrLength: Int, ) { notImplemented() } override fun updateObject( columnLabel: String?, x: Any?, ) { notImplemented() } override fun afterLast() { internalMove(resultSetLength + 1) } override fun updateLong( columnIndex: Int, x: Long, ) { notImplemented() } override fun updateLong( columnLabel: String?, x: Long, ) { notImplemented() } override fun getBlob(columnIndex: Int): Blob { // TODO Maybe? notImplemented() } override fun getBlob(columnLabel: String?): Blob { // TODO Maybe? notImplemented() } override fun updateClob( columnIndex: Int, x: Clob?, ) { notImplemented() } override fun updateClob( columnLabel: String?, x: Clob?, ) { notImplemented() } override fun updateClob( columnIndex: Int, reader: Reader?, length: Long, ) { notImplemented() } override fun updateClob( columnLabel: String?, reader: Reader?, length: Long, ) { notImplemented() } override fun updateClob( columnIndex: Int, reader: Reader?, ) { notImplemented() } override fun updateClob( columnLabel: String?, reader: Reader?, ) { notImplemented() } override fun getByte(columnIndex: Int): Byte = obj(columnIndex) as Byte override fun getByte(columnLabel: String?): Byte = obj(columnLabel) as Byte override fun getString(columnIndex: Int): String? = obj(columnIndex) as String? override fun getString(columnLabel: String?): String? = obj(columnLabel) as String? override fun updateSQLXML( columnIndex: Int, xmlObject: SQLXML?, ) { notImplemented() } override fun updateSQLXML( columnLabel: String?, xmlObject: SQLXML?, ) { notImplemented() } override fun updateDate( columnIndex: Int, x: Date?, ) { notImplemented() } override fun updateDate( columnLabel: String?, x: Date?, ) { notImplemented() } override fun getObject(columnIndex: Int): Any? = obj(columnIndex) override fun getObject(columnLabel: String?): Any? = obj(columnLabel) override fun getObject( columnIndex: Int, map: MutableMap>?, ): Any { // TODO Maybe? notImplemented() } override fun getObject( columnLabel: String?, map: MutableMap>?, ): Any { // TODO Maybe? notImplemented() } override fun getObject( columnIndex: Int, type: Class?, ): T = obj(columnIndex) as T override fun getObject( columnLabel: String?, type: Class?, ): T = obj(columnLabel) as T override fun previous(): Boolean { internalMove(cursor - 1) return cursorValid() } override fun updateDouble( columnIndex: Int, x: Double, ) { notImplemented() } override fun updateDouble( columnLabel: String?, x: Double, ) { notImplemented() } private fun castToLong(obj: Any?): Long { if (obj == null) { return 0 } else if (obj is Long) { return obj } else if (obj is Number) { return obj.toLong() } else { throw IllegalStateException("Object is not a long!") } } override fun getLong(columnIndex: Int): Long = castToLong(obj(columnIndex)) override fun getLong(columnLabel: String?): Long = castToLong(obj(columnLabel)) override fun getClob(columnIndex: Int): Clob { // TODO Maybe? notImplemented() } override fun getClob(columnLabel: String?): Clob { // TODO Maybe? notImplemented() } override fun updateBlob( columnIndex: Int, x: Blob?, ) { notImplemented() } override fun updateBlob( columnLabel: String?, x: Blob?, ) { notImplemented() } override fun updateBlob( columnIndex: Int, inputStream: InputStream?, length: Long, ) { notImplemented() } override fun updateBlob( columnLabel: String?, inputStream: InputStream?, length: Long, ) { notImplemented() } override fun updateBlob( columnIndex: Int, inputStream: InputStream?, ) { notImplemented() } override fun updateBlob( columnLabel: String?, inputStream: InputStream?, ) { notImplemented() } override fun updateByte( columnIndex: Int, x: Byte, ) { notImplemented() } override fun updateByte( columnLabel: String?, x: Byte, ) { notImplemented() } override fun updateRow() { notImplemented() } override fun deleteRow() { notImplemented() } override fun getNString(columnIndex: Int): String = obj(columnIndex) as String override fun getNString(columnLabel: String?): String = obj(columnLabel) as String override fun getArray(columnIndex: Int): Array { // TODO Maybe? notImplemented() } override fun getArray(columnLabel: String?): Array { // TODO Maybe? notImplemented() } override fun cancelRowUpdates() { notImplemented() } override fun updateString( columnIndex: Int, x: String?, ) { notImplemented() } override fun updateString( columnLabel: String?, x: String?, ) { notImplemented() } override fun setFetchDirection(direction: Int) { notImplemented() } override fun getCharacterStream(columnIndex: Int): Reader = getNCharacterStream(columnIndex) override fun getCharacterStream(columnLabel: String?): Reader = getNCharacterStream(columnLabel) override fun isBeforeFirst(): Boolean = cursor - 1 < resultSetLength override fun updateBoolean( columnIndex: Int, x: Boolean, ) { notImplemented() } override fun updateBoolean( columnLabel: String?, x: Boolean, ) { notImplemented() } override fun refreshRow() { notImplemented() } override fun rowUpdated() = false override fun updateBigDecimal( columnIndex: Int, x: BigDecimal?, ) { notImplemented() } override fun updateBigDecimal( columnLabel: String?, x: BigDecimal?, ) { notImplemented() } override fun getShort(columnIndex: Int): Short = obj(columnIndex) as Short override fun getShort(columnLabel: String?): Short = obj(columnLabel) as Short override fun getAsciiStream(columnIndex: Int): InputStream = getBinaryStream(columnIndex) override fun getAsciiStream(columnLabel: String?): InputStream = getBinaryStream(columnLabel) override fun updateTime( columnIndex: Int, x: Time?, ) { notImplemented() } override fun updateTime( columnLabel: String?, x: Time?, ) { notImplemented() } override fun getTimestamp(columnIndex: Int): Timestamp { // TODO Maybe? notImplemented() } override fun getTimestamp(columnLabel: String?): Timestamp { // TODO Maybe? notImplemented() } override fun getTimestamp( columnIndex: Int, cal: Calendar?, ): Timestamp { // TODO Maybe? notImplemented() } override fun getTimestamp( columnLabel: String?, cal: Calendar?, ): Timestamp { // TODO Maybe? notImplemented() } override fun getRef(columnIndex: Int): Ref { // TODO Maybe? notImplemented() } override fun getRef(columnLabel: String?): Ref { // TODO Maybe? notImplemented() } override fun getConcurrency() = ResultSet.CONCUR_READ_ONLY override fun updateRowId( columnIndex: Int, x: RowId?, ) { notImplemented() } override fun updateRowId( columnLabel: String?, x: RowId?, ) { notImplemented() } override fun getNCharacterStream(columnIndex: Int): Reader = getBinaryStream(columnIndex).reader() override fun getNCharacterStream(columnLabel: String?): Reader = getBinaryStream(columnLabel).reader() override fun updateArray( columnIndex: Int, x: Array?, ) { notImplemented() } override fun updateArray( columnLabel: String?, x: Array?, ) { notImplemented() } override fun getBytes(columnIndex: Int): ByteArray = obj(columnIndex) as ByteArray override fun getBytes(columnLabel: String?): ByteArray = obj(columnLabel) as ByteArray override fun getDouble(columnIndex: Int): Double = obj(columnIndex) as Double override fun getDouble(columnLabel: String?): Double = obj(columnLabel) as Double override fun getUnicodeStream(columnIndex: Int): InputStream = getBinaryStream(columnIndex) override fun getUnicodeStream(columnLabel: String?): InputStream = getBinaryStream(columnLabel) override fun rowInserted() = false private fun thisIsWrapperFor(iface: Class<*>?) = this.javaClass.isInstance(iface) override fun isWrapperFor(iface: Class<*>?): Boolean = thisIsWrapperFor(iface) || parent.isWrapperFor(iface) override fun getInt(columnIndex: Int): Int = obj(columnIndex) as Int override fun getInt(columnLabel: String?): Int = obj(columnLabel) as Int override fun updateNull(columnIndex: Int) { notImplemented() } override fun updateNull(columnLabel: String?) { notImplemented() } override fun getRowId(columnIndex: Int): RowId { // TODO Maybe? notImplemented() } override fun getRowId(columnLabel: String?): RowId { // TODO Maybe? notImplemented() } override fun getMetaData(): ResultSetMetaData = object : ResultSetMetaData by parentMetadata { override fun isReadOnly(column: Int) = true override fun isWritable(column: Int) = false override fun isDefinitelyWritable(column: Int) = false override fun getColumnCount() = this@ScrollableResultSet.columnCount override fun getColumnLabel(column: Int): String = columnLabels[column - 1] } override fun getBinaryStream(columnIndex: Int): InputStream = (obj(columnIndex) as ByteArray).inputStream() override fun getBinaryStream(columnLabel: String?): InputStream = (obj(columnLabel) as ByteArray).inputStream() override fun updateCharacterStream( columnIndex: Int, x: Reader?, length: Int, ) { notImplemented() } override fun updateCharacterStream( columnLabel: String?, reader: Reader?, length: Int, ) { notImplemented() } override fun updateCharacterStream( columnIndex: Int, x: Reader?, length: Long, ) { notImplemented() } override fun updateCharacterStream( columnLabel: String?, reader: Reader?, length: Long, ) { notImplemented() } override fun updateCharacterStream( columnIndex: Int, x: Reader?, ) { notImplemented() } override fun updateCharacterStream( columnLabel: String?, reader: Reader?, ) { notImplemented() } class ResultSetEntry { val data = mutableListOf() } }