1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5
6package org.chromium.chrome.browser.database;
7
8import android.database.AbstractCursor;
9import android.database.CursorWindow;
10import android.util.Log;
11
12import org.chromium.base.CalledByNative;
13
14import java.sql.Types;
15
16/**
17 * This class exposes the query result from native side.
18 */
19public class SQLiteCursor extends AbstractCursor {
20    private static final String TAG = "SQLiteCursor";
21    // Used by JNI.
22    private int mNativeSQLiteCursor;
23
24    // The count of result rows.
25    private int mCount = -1;
26
27    private int[] mColumnTypes;
28
29    private final Object mColumnTypeLock = new Object();
30
31    // The belows are the locks for those methods that need wait for
32    // the callback result in native side.
33    private final Object mMoveLock = new Object();
34    private final Object mGetBlobLock = new Object();
35
36    private SQLiteCursor(int nativeSQLiteCursor) {
37        mNativeSQLiteCursor = nativeSQLiteCursor;
38    }
39
40    @CalledByNative
41    private static SQLiteCursor create(int nativeSQLiteCursor) {
42        return new SQLiteCursor(nativeSQLiteCursor);
43    }
44
45    @Override
46    public int getCount() {
47        synchronized (mMoveLock) {
48            if (mCount == -1)
49                mCount = nativeGetCount(mNativeSQLiteCursor);
50        }
51        return mCount;
52    }
53
54    @Override
55    public String[] getColumnNames() {
56        return nativeGetColumnNames(mNativeSQLiteCursor);
57    }
58
59    @Override
60    public String getString(int column) {
61        return nativeGetString(mNativeSQLiteCursor, column);
62    }
63
64    @Override
65    public short getShort(int column) {
66        return (short) nativeGetInt(mNativeSQLiteCursor, column);
67    }
68
69    @Override
70    public int getInt(int column) {
71        return nativeGetInt(mNativeSQLiteCursor, column);
72    }
73
74    @Override
75    public long getLong(int column) {
76        return nativeGetLong(mNativeSQLiteCursor, column);
77    }
78
79    @Override
80    public float getFloat(int column) {
81        return (float)nativeGetDouble(mNativeSQLiteCursor, column);
82    }
83
84    @Override
85    public double getDouble(int column) {
86        return nativeGetDouble(mNativeSQLiteCursor, column);
87    }
88
89    @Override
90    public boolean isNull(int column) {
91        return nativeIsNull(mNativeSQLiteCursor, column);
92    }
93
94    @Override
95    public void close() {
96        super.close();
97        nativeDestroy(mNativeSQLiteCursor);
98        mNativeSQLiteCursor = 0;
99    }
100
101    @Override
102    public boolean onMove(int oldPosition, int newPosition) {
103        synchronized (mMoveLock) {
104            nativeMoveTo(mNativeSQLiteCursor, newPosition);
105        }
106        return super.onMove(oldPosition, newPosition);
107    }
108
109    @Override
110    public byte[] getBlob(int column) {
111        synchronized (mGetBlobLock) {
112            return nativeGetBlob(mNativeSQLiteCursor, column);
113        }
114    }
115
116    @Deprecated
117    public boolean supportsUpdates() {
118        return false;
119    }
120
121    @Override
122    protected void finalize() {
123        super.finalize();
124        if (!isClosed()) {
125            Log.w(TAG, "Cursor hasn't been closed");
126            close();
127        }
128    }
129
130    @Override
131    public void fillWindow(int position, CursorWindow window) {
132        if (position < 0 || position > getCount()) {
133            return;
134        }
135        window.acquireReference();
136        try {
137            int oldpos = mPos;
138            mPos = position - 1;
139            window.clear();
140            window.setStartPosition(position);
141            int columnNum = getColumnCount();
142            window.setNumColumns(columnNum);
143            while (moveToNext() && window.allocRow()) {
144                for (int i = 0; i < columnNum; i++) {
145                    boolean hasRoom = true;
146                    switch (getColumnType(i)) {
147                        case Types.DOUBLE:
148                            hasRoom = fillRow(window, Double.valueOf(getDouble(i)), mPos, i);
149                            break;
150                        case Types.NUMERIC:
151                            hasRoom = fillRow(window, Long.valueOf(getLong(i)), mPos, i);
152                            break;
153                        case Types.BLOB:
154                            hasRoom = fillRow(window, getBlob(i), mPos, i);
155                            break;
156                        case Types.LONGVARCHAR:
157                            hasRoom = fillRow(window, getString(i), mPos, i);
158                            break;
159                        case Types.NULL:
160                            hasRoom = fillRow(window, null, mPos, i);
161                            break;
162                    }
163                    if (!hasRoom) {
164                        break;
165                    }
166                }
167            }
168            mPos = oldpos;
169        } catch (IllegalStateException e) {
170            // simply ignore it
171        } finally {
172            window.releaseReference();
173        }
174    }
175
176    /**
177     * Fill row with the given value. If the value type is other than Long,
178     * String, byte[] or Double, the NULL will be filled.
179     *
180     * @return true if succeeded, false if window is full.
181     */
182    private boolean fillRow(CursorWindow window, Object value, int pos, int column) {
183        if (putValue(window, value, pos, column)) {
184            return true;
185        } else {
186            window.freeLastRow();
187            return false;
188        }
189    }
190
191    /**
192     * Put the value in given window. If the value type is other than Long,
193     * String, byte[] or Double, the NULL will be filled.
194     *
195     * @return true if succeeded.
196     */
197    private boolean putValue(CursorWindow window, Object value, int pos, int column) {
198        if (value == null) {
199            return window.putNull(pos, column);
200        } else if (value instanceof Long) {
201            return window.putLong((Long) value, pos, column);
202        } else if (value instanceof String) {
203            return window.putString((String) value, pos, column);
204        } else if (value instanceof byte[] && ((byte[]) value).length > 0) {
205            return window.putBlob((byte[]) value, pos, column);
206        } else if (value instanceof Double) {
207            return window.putDouble((Double) value, pos, column);
208        } else {
209            return window.putNull(pos, column);
210        }
211    }
212
213    /**
214     * @param index the column index.
215     * @return the column type from cache or native side.
216     */
217    private int getColumnType(int index) {
218        synchronized (mColumnTypeLock) {
219            if (mColumnTypes == null) {
220                int columnCount = getColumnCount();
221                mColumnTypes = new int[columnCount];
222                for (int i = 0; i < columnCount; i++) {
223                    mColumnTypes[i] = nativeGetColumnType(mNativeSQLiteCursor, i);
224                }
225            }
226        }
227        return mColumnTypes[index];
228    }
229
230    private native void nativeDestroy(int nativeSQLiteCursor);
231    private native int nativeGetCount(int nativeSQLiteCursor);
232    private native String[] nativeGetColumnNames(int nativeSQLiteCursor);
233    private native int nativeGetColumnType(int nativeSQLiteCursor, int column);
234    private native String nativeGetString(int nativeSQLiteCursor, int column);
235    private native byte[] nativeGetBlob(int nativeSQLiteCursor, int column);
236    private native boolean nativeIsNull(int nativeSQLiteCursor, int column);
237    private native long nativeGetLong(int nativeSQLiteCursor, int column);
238    private native int nativeGetInt(int nativeSQLiteCursor, int column);
239    private native double nativeGetDouble(int nativeSQLiteCursor, int column);
240    private native int nativeMoveTo(int nativeSQLiteCursor, int newPosition);
241}
242