Home | History | Annotate | Download | only in shadows
      1 package com.xtremelabs.robolectric.shadows;
      2 
      3 import android.database.sqlite.SQLiteCursor;
      4 import com.xtremelabs.robolectric.internal.Implementation;
      5 import com.xtremelabs.robolectric.internal.Implements;
      6 
      7 import java.sql.Clob;
      8 import java.sql.Connection;
      9 import java.sql.ResultSet;
     10 import java.sql.ResultSetMetaData;
     11 import java.sql.SQLException;
     12 import java.sql.Statement;
     13 import java.util.HashMap;
     14 import java.util.Map;
     15 
     16 /**
     17  * Simulates an Android Cursor object, by wrapping a JDBC ResultSet.
     18  */
     19 @Implements(SQLiteCursor.class)
     20 public 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 moveToLast() {
     73         return super.moveToLast();
     74     }
     75 
     76     @Implementation
     77     @Override
     78     public final boolean moveToFirst() {
     79         return super.moveToFirst();
     80     }
     81 
     82     @Implementation
     83     @Override
     84     public boolean moveToNext() {
     85         return super.moveToNext();
     86     }
     87 
     88     @Implementation
     89     @Override
     90     public boolean moveToPrevious() {
     91         return super.moveToPrevious();
     92     }
     93 
     94     @Implementation
     95     @Override
     96     public boolean moveToPosition(int pos) {
     97     	return super.moveToPosition(pos);
     98     }
     99 
    100     @Implementation
    101     public byte[] getBlob(int columnIndex) {
    102     	checkPosition();
    103         return (byte[]) this.currentRow.get(getColumnNames()[columnIndex]);
    104     }
    105 
    106     @Implementation
    107     public String getString(int columnIndex) {
    108         checkPosition();
    109         Object value = this.currentRow.get(getColumnNames()[columnIndex]);
    110         if (value instanceof Clob) {
    111             try {
    112                 return ((Clob) value).getSubString(1, (int)((Clob) value).length());
    113             } catch (SQLException x) {
    114                 throw new RuntimeException(x);
    115             }
    116         } else {
    117             return (String)value;
    118         }
    119     }
    120 
    121 	@Implementation
    122 	public short getShort(int columnIndex) {
    123 		checkPosition();
    124 		Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    125     	if (o==null) return 0;
    126         return new Short(o.toString());
    127 	}
    128 
    129     @Implementation
    130     public int getInt(int columnIndex) {
    131     	checkPosition();
    132     	Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    133     	if (o==null) return 0;
    134         return new Integer(o.toString());
    135     }
    136 
    137     @Implementation
    138     public long getLong(int columnIndex) {
    139     	checkPosition();
    140     	Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    141     	if (o==null) return 0;
    142         return new Long(o.toString());
    143     }
    144 
    145     @Implementation
    146     public float getFloat(int columnIndex) {
    147     	checkPosition();
    148     	Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    149     	if (o==null) return 0;
    150         return new Float(o.toString());
    151 
    152     }
    153 
    154     @Implementation
    155     public double getDouble(int columnIndex) {
    156     	checkPosition();
    157     	Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    158     	if (o==null) return 0;
    159     	return new Double(o.toString());
    160     }
    161 
    162     private void checkPosition() {
    163         if (-1 == currentRowNumber || getCount() == currentRowNumber) {
    164             throw new IndexOutOfBoundsException(currentRowNumber + " " + getCount());
    165         }
    166     }
    167 
    168     @Implementation
    169     public void close() {
    170         if (resultSet == null) {
    171             return;
    172         }
    173 
    174         try {
    175             resultSet.close();
    176             resultSet = null;
    177             rows = null;
    178             currentRow = null;
    179         } catch (SQLException e) {
    180             throw new RuntimeException("SQL exception in close", e);
    181         }
    182     }
    183 
    184     @Implementation
    185     public boolean isClosed() {
    186         return (resultSet == null);
    187     }
    188 
    189     @Implementation
    190     public boolean isNull(int columnIndex) {
    191         Object o = this.currentRow.get(getColumnNames()[columnIndex]);
    192         return o == null;
    193     }
    194 
    195     /**
    196      * Allows test cases access to the underlying JDBC ResultSet, for use in
    197      * assertions.
    198      *
    199      * @return the result set
    200      */
    201     public ResultSet getResultSet() {
    202         return resultSet;
    203     }
    204 
    205     /**
    206      * Allows test cases access to the underlying JDBC ResultSetMetaData, for use in
    207      * assertions. Available even if cl
    208      *
    209      * @return the result set
    210      */
    211     public ResultSet getResultSetMetaData() {
    212         return resultSet;
    213     }
    214 
    215     /**
    216      * loads a row's values
    217      * @param rs
    218      * @return
    219      * @throws SQLException
    220      */
    221     private Map<String,Object> fillRowValues(ResultSet rs) throws SQLException {
    222     	Map<String,Object> row = new HashMap<String,Object>();
    223     	for (String s : getColumnNames()) {
    224 			  row.put(s, rs.getObject(s));
    225     	}
    226     	return row;
    227     }
    228     private void fillRows(String sql, Connection connection) throws SQLException {
    229     	//ResultSets in SQLite\Android are only TYPE_FORWARD_ONLY. Android caches results in the WindowedCursor to allow moveToPrevious() to function.
    230     	//Robolectric will have to cache the results too. In the rows map.
    231     	Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    232         ResultSet rs = statement.executeQuery(sql);
    233         int count = 0;
    234         if (rs.next()) {
    235         	     do {
    236         	    	Map<String,Object> row = fillRowValues(rs);
    237          	    	rows.put(count, row);
    238         	    	count++;
    239         	     } while (rs.next());
    240         	 } else {
    241         		 rs.close();
    242         	 }
    243 
    244         rowCount = count;
    245 
    246     }
    247 
    248     public void setResultSet(ResultSet result, String sql) {
    249         this.resultSet = result;
    250         rowCount = 0;
    251 
    252         //Cache all rows.  Caching rows should be thought of as a simple replacement for ShadowCursorWindow
    253         if (resultSet != null) {
    254         	cacheColumnNames(resultSet);
    255         	try {
    256         		fillRows(sql, result.getStatement().getConnection());
    257 			} catch (SQLException e) {
    258 			    throw new RuntimeException("SQL exception in setResultSet", e);
    259 			}
    260         }
    261     }
    262 }
    263