package com.xtremelabs.robolectric.shadows; import android.database.sqlite.SQLiteCursor; import com.xtremelabs.robolectric.internal.Implementation; import com.xtremelabs.robolectric.internal.Implements; import java.sql.Clob; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.Map; /** * Simulates an Android Cursor object, by wrapping a JDBC ResultSet. */ @Implements(SQLiteCursor.class) public class ShadowSQLiteCursor extends ShadowAbstractCursor { private ResultSet resultSet; /** * Stores the column names so they are retrievable after the resultSet has closed */ private void cacheColumnNames(ResultSet rs) { try { ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); columnNameArray = new String[columnCount]; for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) { String cName = metaData.getColumnName(columnIndex).toLowerCase(); this.columnNames.put(cName, columnIndex-1); this.columnNameArray[columnIndex-1]=cName; } } catch (SQLException e) { throw new RuntimeException("SQL exception in cacheColumnNames", e); } } private Integer getColIndex(String columnName) { if (columnName == null) { return -1; } Integer i = this.columnNames.get(columnName.toLowerCase()); if (i==null) return -1; return i; } @Implementation public int getColumnIndex(String columnName) { return getColIndex(columnName); } @Implementation public int getColumnIndexOrThrow(String columnName) { Integer columnIndex = getColIndex(columnName); if (columnIndex == -1) { throw new IllegalArgumentException("Column index does not exist"); } return columnIndex; } @Implementation @Override public final boolean moveToLast() { return super.moveToLast(); } @Implementation @Override public final boolean moveToFirst() { return super.moveToFirst(); } @Implementation @Override public boolean moveToNext() { return super.moveToNext(); } @Implementation @Override public boolean moveToPrevious() { return super.moveToPrevious(); } @Implementation @Override public boolean moveToPosition(int pos) { return super.moveToPosition(pos); } @Implementation public byte[] getBlob(int columnIndex) { checkPosition(); return (byte[]) this.currentRow.get(getColumnNames()[columnIndex]); } @Implementation public String getString(int columnIndex) { checkPosition(); Object value = this.currentRow.get(getColumnNames()[columnIndex]); if (value instanceof Clob) { try { return ((Clob) value).getSubString(1, (int)((Clob) value).length()); } catch (SQLException x) { throw new RuntimeException(x); } } else { return (String)value; } } @Implementation public short getShort(int columnIndex) { checkPosition(); Object o =this.currentRow.get(getColumnNames()[columnIndex]); if (o==null) return 0; return new Short(o.toString()); } @Implementation public int getInt(int columnIndex) { checkPosition(); Object o =this.currentRow.get(getColumnNames()[columnIndex]); if (o==null) return 0; return new Integer(o.toString()); } @Implementation public long getLong(int columnIndex) { checkPosition(); Object o =this.currentRow.get(getColumnNames()[columnIndex]); if (o==null) return 0; return new Long(o.toString()); } @Implementation public float getFloat(int columnIndex) { checkPosition(); Object o =this.currentRow.get(getColumnNames()[columnIndex]); if (o==null) return 0; return new Float(o.toString()); } @Implementation public double getDouble(int columnIndex) { checkPosition(); Object o =this.currentRow.get(getColumnNames()[columnIndex]); if (o==null) return 0; return new Double(o.toString()); } private void checkPosition() { if (-1 == currentRowNumber || getCount() == currentRowNumber) { throw new IndexOutOfBoundsException(currentRowNumber + " " + getCount()); } } @Implementation public void close() { if (resultSet == null) { return; } try { resultSet.close(); resultSet = null; rows = null; currentRow = null; } catch (SQLException e) { throw new RuntimeException("SQL exception in close", e); } } @Implementation public boolean isClosed() { return (resultSet == null); } @Implementation public boolean isNull(int columnIndex) { Object o = this.currentRow.get(getColumnNames()[columnIndex]); return o == null; } /** * Allows test cases access to the underlying JDBC ResultSet, for use in * assertions. * * @return the result set */ public ResultSet getResultSet() { return resultSet; } /** * Allows test cases access to the underlying JDBC ResultSetMetaData, for use in * assertions. Available even if cl * * @return the result set */ public ResultSet getResultSetMetaData() { return resultSet; } /** * loads a row's values * @param rs * @return * @throws SQLException */ private Map fillRowValues(ResultSet rs) throws SQLException { Map row = new HashMap(); for (String s : getColumnNames()) { row.put(s, rs.getObject(s)); } return row; } private void fillRows(String sql, Connection connection) throws SQLException { //ResultSets in SQLite\Android are only TYPE_FORWARD_ONLY. Android caches results in the WindowedCursor to allow moveToPrevious() to function. //Robolectric will have to cache the results too. In the rows map. Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); ResultSet rs = statement.executeQuery(sql); int count = 0; if (rs.next()) { do { Map row = fillRowValues(rs); rows.put(count, row); count++; } while (rs.next()); } else { rs.close(); } rowCount = count; } public void setResultSet(ResultSet result, String sql) { this.resultSet = result; rowCount = 0; //Cache all rows. Caching rows should be thought of as a simple replacement for ShadowCursorWindow if (resultSet != null) { cacheColumnNames(resultSet); try { fillRows(sql, result.getStatement().getConnection()); } catch (SQLException e) { throw new RuntimeException("SQL exception in setResultSet", e); } } } }