ShadowSQLiteCursor.java revision 320946cbbc34c5db5a856eb5581b5e3280c2c5f3
1package com.xtremelabs.robolectric.shadows; 2 3import android.database.sqlite.SQLiteCursor; 4import com.xtremelabs.robolectric.internal.Implementation; 5import com.xtremelabs.robolectric.internal.Implements; 6 7import java.sql.Clob; 8import java.sql.Connection; 9import java.sql.ResultSet; 10import java.sql.ResultSetMetaData; 11import java.sql.SQLException; 12import java.sql.Statement; 13import java.util.HashMap; 14import java.util.Map; 15 16/** 17 * Simulates an Android Cursor object, by wrapping a JDBC ResultSet. 18 */ 19@Implements(SQLiteCursor.class) 20public class ShadowSQLiteCursor extends ShadowAbstractCursor { 21 22 private ResultSet resultSet; 23 24 25 /** 26 * Stores the column names so they are retrievable after the resultSet has closed 27 */ 28 private void cacheColumnNames(ResultSet rs) { 29 try { 30 ResultSetMetaData metaData = rs.getMetaData(); 31 int columnCount = metaData.getColumnCount(); 32 columnNameArray = new String[columnCount]; 33 for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) { 34 String cName = metaData.getColumnName(columnIndex).toLowerCase(); 35 this.columnNames.put(cName, columnIndex-1); 36 this.columnNameArray[columnIndex-1]=cName; 37 } 38 } catch (SQLException e) { 39 throw new RuntimeException("SQL exception in cacheColumnNames", e); 40 } 41 } 42 43 44 45 46 private Integer getColIndex(String columnName) { 47 if (columnName == null) { 48 return -1; 49 } 50 51 Integer i = this.columnNames.get(columnName.toLowerCase()); 52 if (i==null) return -1; 53 return i; 54 } 55 56 @Implementation 57 public int getColumnIndex(String columnName) { 58 return getColIndex(columnName); 59 } 60 61 @Implementation 62 public int getColumnIndexOrThrow(String columnName) { 63 Integer columnIndex = getColIndex(columnName); 64 if (columnIndex == -1) { 65 throw new IllegalArgumentException("Column index does not exist"); 66 } 67 return columnIndex; 68 } 69 70 @Implementation 71 @Override 72 public final boolean moveToFirst() { 73 return super.moveToFirst(); 74 } 75 76 @Implementation 77 @Override 78 public boolean moveToNext() { 79 return super.moveToNext(); 80 } 81 82 @Implementation 83 @Override 84 public boolean moveToPrevious() { 85 return super.moveToPrevious(); 86 } 87 88 @Implementation 89 @Override 90 public boolean moveToPosition(int pos) { 91 return super.moveToPosition(pos); 92 } 93 94 @Implementation 95 public byte[] getBlob(int columnIndex) { 96 checkPosition(); 97 return (byte[]) this.currentRow.get(getColumnNames()[columnIndex]); 98 } 99 100 @Implementation 101 public String getString(int columnIndex) { 102 checkPosition(); 103 Object value = this.currentRow.get(getColumnNames()[columnIndex]); 104 if (value instanceof Clob) { 105 try { 106 return ((Clob) value).getSubString(1, (int)((Clob) value).length()); 107 } catch (SQLException x) { 108 throw new RuntimeException(x); 109 } 110 } else { 111 return (String)value; 112 } 113 } 114 115 @Implementation 116 public short getShort(int columnIndex) { 117 checkPosition(); 118 Object o =this.currentRow.get(getColumnNames()[columnIndex]); 119 if (o==null) return 0; 120 return new Short(o.toString()); 121 } 122 123 @Implementation 124 public int getInt(int columnIndex) { 125 checkPosition(); 126 Object o =this.currentRow.get(getColumnNames()[columnIndex]); 127 if (o==null) return 0; 128 return new Integer(o.toString()); 129 } 130 131 @Implementation 132 public long getLong(int columnIndex) { 133 checkPosition(); 134 Object o =this.currentRow.get(getColumnNames()[columnIndex]); 135 if (o==null) return 0; 136 return new Long(o.toString()); 137 } 138 139 @Implementation 140 public float getFloat(int columnIndex) { 141 checkPosition(); 142 Object o =this.currentRow.get(getColumnNames()[columnIndex]); 143 if (o==null) return 0; 144 return new Float(o.toString()); 145 146 } 147 148 @Implementation 149 public double getDouble(int columnIndex) { 150 checkPosition(); 151 Object o =this.currentRow.get(getColumnNames()[columnIndex]); 152 if (o==null) return 0; 153 return new Double(o.toString()); 154 } 155 156 private void checkPosition() { 157 if (-1 == currentRowNumber || getCount() == currentRowNumber) { 158 throw new IndexOutOfBoundsException(currentRowNumber + " " + getCount()); 159 } 160 } 161 162 @Implementation 163 public void close() { 164 if (resultSet == null) { 165 return; 166 } 167 168 try { 169 resultSet.close(); 170 resultSet = null; 171 rows = null; 172 currentRow = null; 173 } catch (SQLException e) { 174 throw new RuntimeException("SQL exception in close", e); 175 } 176 } 177 178 @Implementation 179 public boolean isClosed() { 180 return (resultSet == null); 181 } 182 183 @Implementation 184 public boolean isNull(int columnIndex) { 185 Object o = this.currentRow.get(getColumnNames()[columnIndex]); 186 return o == null; 187 } 188 189 /** 190 * Allows test cases access to the underlying JDBC ResultSet, for use in 191 * assertions. 192 * 193 * @return the result set 194 */ 195 public ResultSet getResultSet() { 196 return resultSet; 197 } 198 199 /** 200 * Allows test cases access to the underlying JDBC ResultSetMetaData, for use in 201 * assertions. Available even if cl 202 * 203 * @return the result set 204 */ 205 public ResultSet getResultSetMetaData() { 206 return resultSet; 207 } 208 209 /** 210 * loads a row's values 211 * @param rs 212 * @return 213 * @throws SQLException 214 */ 215 private Map<String,Object> fillRowValues(ResultSet rs) throws SQLException { 216 Map<String,Object> row = new HashMap<String,Object>(); 217 for (String s : getColumnNames()) { 218 row.put(s, rs.getObject(s)); 219 } 220 return row; 221 } 222 private void fillRows(String sql, Connection connection) throws SQLException { 223 //ResultSets in SQLite\Android are only TYPE_FORWARD_ONLY. Android caches results in the WindowedCursor to allow moveToPrevious() to function. 224 //Robolectric will have to cache the results too. In the rows map. 225 Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 226 ResultSet rs = statement.executeQuery(sql); 227 int count = 0; 228 if (rs.next()) { 229 do { 230 Map<String,Object> row = fillRowValues(rs); 231 rows.put(count, row); 232 count++; 233 } while (rs.next()); 234 } else { 235 rs.close(); 236 } 237 238 rowCount = count; 239 240 } 241 242 public void setResultSet(ResultSet result, String sql) { 243 this.resultSet = result; 244 rowCount = 0; 245 246 //Cache all rows. Caching rows should be thought of as a simple replacement for ShadowCursorWindow 247 if (resultSet != null) { 248 cacheColumnNames(resultSet); 249 try { 250 fillRows(sql, result.getStatement().getConnection()); 251 } catch (SQLException e) { 252 throw new RuntimeException("SQL exception in setResultSet", e); 253 } 254 } 255 } 256} 257