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