| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| |
| package org.chromium.chrome.browser.database; |
| |
| import android.database.AbstractCursor; |
| import android.database.CursorWindow; |
| import android.util.Log; |
| |
| import org.chromium.base.CalledByNative; |
| |
| import java.sql.Types; |
| |
| /** |
| * This class exposes the query result from native side. |
| */ |
| public class SQLiteCursor extends AbstractCursor { |
| private static final String TAG = "SQLiteCursor"; |
| // Used by JNI. |
| private long mNativeSQLiteCursor; |
| |
| // The count of result rows. |
| private int mCount = -1; |
| |
| private int[] mColumnTypes; |
| |
| private final Object mColumnTypeLock = new Object(); |
| private final Object mDestoryNativeLock = new Object(); |
| |
| // The belows are the locks for those methods that need wait for |
| // the callback result in native side. |
| private final Object mMoveLock = new Object(); |
| private final Object mGetBlobLock = new Object(); |
| |
| private SQLiteCursor(long nativeSQLiteCursor) { |
| mNativeSQLiteCursor = nativeSQLiteCursor; |
| } |
| |
| @CalledByNative |
| private static SQLiteCursor create(long nativeSQLiteCursor) { |
| return new SQLiteCursor(nativeSQLiteCursor); |
| } |
| |
| @Override |
| public int getCount() { |
| synchronized (mMoveLock) { |
| if (mCount == -1) |
| mCount = nativeGetCount(mNativeSQLiteCursor); |
| } |
| return mCount; |
| } |
| |
| @Override |
| public String[] getColumnNames() { |
| return nativeGetColumnNames(mNativeSQLiteCursor); |
| } |
| |
| @Override |
| public String getString(int column) { |
| return nativeGetString(mNativeSQLiteCursor, column); |
| } |
| |
| @Override |
| public short getShort(int column) { |
| return (short) nativeGetInt(mNativeSQLiteCursor, column); |
| } |
| |
| @Override |
| public int getInt(int column) { |
| return nativeGetInt(mNativeSQLiteCursor, column); |
| } |
| |
| @Override |
| public long getLong(int column) { |
| return nativeGetLong(mNativeSQLiteCursor, column); |
| } |
| |
| @Override |
| public float getFloat(int column) { |
| return (float) nativeGetDouble(mNativeSQLiteCursor, column); |
| } |
| |
| @Override |
| public double getDouble(int column) { |
| return nativeGetDouble(mNativeSQLiteCursor, column); |
| } |
| |
| @Override |
| public boolean isNull(int column) { |
| return nativeIsNull(mNativeSQLiteCursor, column); |
| } |
| |
| @Override |
| public void close() { |
| super.close(); |
| synchronized (mDestoryNativeLock) { |
| if (mNativeSQLiteCursor != 0) { |
| nativeDestroy(mNativeSQLiteCursor); |
| mNativeSQLiteCursor = 0; |
| } |
| } |
| } |
| |
| @Override |
| public boolean onMove(int oldPosition, int newPosition) { |
| synchronized (mMoveLock) { |
| nativeMoveTo(mNativeSQLiteCursor, newPosition); |
| } |
| return super.onMove(oldPosition, newPosition); |
| } |
| |
| @Override |
| public byte[] getBlob(int column) { |
| synchronized (mGetBlobLock) { |
| return nativeGetBlob(mNativeSQLiteCursor, column); |
| } |
| } |
| |
| @Deprecated |
| public boolean supportsUpdates() { |
| return false; |
| } |
| |
| @Override |
| protected void finalize() { |
| super.finalize(); |
| if (!isClosed()) { |
| Log.w(TAG, "Cursor hasn't been closed"); |
| close(); |
| } |
| } |
| |
| @Override |
| public void fillWindow(int position, CursorWindow window) { |
| if (position < 0 || position > getCount()) { |
| return; |
| } |
| window.acquireReference(); |
| try { |
| int oldpos = mPos; |
| mPos = position - 1; |
| window.clear(); |
| window.setStartPosition(position); |
| int columnNum = getColumnCount(); |
| window.setNumColumns(columnNum); |
| while (moveToNext() && window.allocRow()) { |
| for (int i = 0; i < columnNum; i++) { |
| boolean hasRoom = true; |
| switch (getColumnType(i)) { |
| case Types.DOUBLE: |
| hasRoom = fillRow(window, Double.valueOf(getDouble(i)), mPos, i); |
| break; |
| case Types.NUMERIC: |
| hasRoom = fillRow(window, Long.valueOf(getLong(i)), mPos, i); |
| break; |
| case Types.BLOB: |
| hasRoom = fillRow(window, getBlob(i), mPos, i); |
| break; |
| case Types.LONGVARCHAR: |
| hasRoom = fillRow(window, getString(i), mPos, i); |
| break; |
| case Types.NULL: |
| hasRoom = fillRow(window, null, mPos, i); |
| break; |
| } |
| if (!hasRoom) { |
| break; |
| } |
| } |
| } |
| mPos = oldpos; |
| } catch (IllegalStateException e) { |
| // simply ignore it |
| } finally { |
| window.releaseReference(); |
| } |
| } |
| |
| /** |
| * Fill row with the given value. If the value type is other than Long, |
| * String, byte[] or Double, the NULL will be filled. |
| * |
| * @return true if succeeded, false if window is full. |
| */ |
| private boolean fillRow(CursorWindow window, Object value, int pos, int column) { |
| if (putValue(window, value, pos, column)) { |
| return true; |
| } else { |
| window.freeLastRow(); |
| return false; |
| } |
| } |
| |
| /** |
| * Put the value in given window. If the value type is other than Long, |
| * String, byte[] or Double, the NULL will be filled. |
| * |
| * @return true if succeeded. |
| */ |
| private boolean putValue(CursorWindow window, Object value, int pos, int column) { |
| if (value == null) { |
| return window.putNull(pos, column); |
| } else if (value instanceof Long) { |
| return window.putLong((Long) value, pos, column); |
| } else if (value instanceof String) { |
| return window.putString((String) value, pos, column); |
| } else if (value instanceof byte[] && ((byte[]) value).length > 0) { |
| return window.putBlob((byte[]) value, pos, column); |
| } else if (value instanceof Double) { |
| return window.putDouble((Double) value, pos, column); |
| } else { |
| return window.putNull(pos, column); |
| } |
| } |
| |
| /** |
| * @param index the column index. |
| * @return the column type from cache or native side. |
| */ |
| private int getColumnType(int index) { |
| synchronized (mColumnTypeLock) { |
| if (mColumnTypes == null) { |
| int columnCount = getColumnCount(); |
| mColumnTypes = new int[columnCount]; |
| for (int i = 0; i < columnCount; i++) { |
| mColumnTypes[i] = nativeGetColumnType(mNativeSQLiteCursor, i); |
| } |
| } |
| } |
| return mColumnTypes[index]; |
| } |
| |
| private native void nativeDestroy(long nativeSQLiteCursor); |
| private native int nativeGetCount(long nativeSQLiteCursor); |
| private native String[] nativeGetColumnNames(long nativeSQLiteCursor); |
| private native int nativeGetColumnType(long nativeSQLiteCursor, int column); |
| private native String nativeGetString(long nativeSQLiteCursor, int column); |
| private native byte[] nativeGetBlob(long nativeSQLiteCursor, int column); |
| private native boolean nativeIsNull(long nativeSQLiteCursor, int column); |
| private native long nativeGetLong(long nativeSQLiteCursor, int column); |
| private native int nativeGetInt(long nativeSQLiteCursor, int column); |
| private native double nativeGetDouble(long nativeSQLiteCursor, int column); |
| private native int nativeMoveTo(long nativeSQLiteCursor, int newPosition); |
| } |