All Downloads are FREE. Search and download functionalities are using the official Maven repository.

android.database.CursorWindow Maven / Gradle / Ivy

/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.database;

import android.annotation.BytesLong;
import android.annotation.IntRange;
import android.database.sqlite.SQLiteClosable;
import android.database.sqlite.SQLiteException;
import android.os.Parcel;
import com.google.common.base.Preconditions;
import org.sqlite.core.Codes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * A buffer containing multiple cursor rows.
 * 

* A {@link CursorWindow} is read-write when initially created and used locally. * When sent to a remote process (by writing it to a {@link Parcel}), the remote process * receives a read-only view of the cursor window. Typically the cursor window * will be allocated by the producer, filled with data, and then sent to the * consumer for reading. *

*/ public class CursorWindow extends SQLiteClosable { private final List rows; private int numColumns; private static final String STATS_TAG = "CursorWindowStats"; // This static member will be evaluated when first used. private static int sCursorWindowSize = -1; private int mStartPos; private final String mName; /** * Creates a new empty cursor window and gives it a name. *

* The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to * set the number of columns before adding any rows to the cursor. *

* * @param name The name of the cursor window, or null if none. */ public CursorWindow(String name) { this(name, getCursorWindowSize()); } /** * Creates a new empty cursor window and gives it a name. *

* The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to * set the number of columns before adding any rows to the cursor. *

* * @param name The name of the cursor window, or null if none. * @param windowSizeBytes Size of cursor window in bytes. * @throws IllegalArgumentException if {@code windowSizeBytes} is less than 0 * @throws AssertionError if created window pointer is 0 *

Note: Memory is dynamically allocated as data rows are added to the * window. Depending on the amount of data stored, the actual amount of memory allocated can be * lower than specified size, but cannot exceed it. */ public CursorWindow(String name, @BytesLong long windowSizeBytes) { if (windowSizeBytes < 0) { throw new IllegalArgumentException("Window size cannot be less than 0"); } mStartPos = 0; mName = name != null && !name.isEmpty() ? name : ""; this.rows = new ArrayList(); } /** * Creates a new empty cursor window. *

* The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to * set the number of columns before adding any rows to the cursor. *

* * @param localWindow True if this window will be used in this process only, * false if it might be sent to another processes. This argument is ignored. * * @deprecated There is no longer a distinction between local and remote * cursor windows. Use the {@link #CursorWindow(String)} constructor instead. */ @Deprecated public CursorWindow(boolean localWindow) { this((String)null); } // private CursorWindow(Parcel source) { // mStartPos = source.readInt(); // mWindowPtr = nativeCreateFromParcel(source); // if (mWindowPtr == 0) { // throw new AssertionError(); // Not possible, the native code won't return it. // } // mName = nativeGetName(mWindowPtr); // mCloseGuard.open("CursorWindow.close"); // } @Override protected void finalize() throws Throwable { try { dispose(); } finally { super.finalize(); } } private void dispose() { rows.clear(); } /** * Gets the name of this cursor window, never null. * @hide */ public String getName() { return mName; } /** * Clears out the existing contents of the window, making it safe to reuse * for new data. *

* The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}), * and number of columns in the cursor are all reset to zero. *

*/ public void clear() { rows.clear(); } /** * Gets the start position of this cursor window. *

* The start position is the zero-based index of the first row that this window contains * relative to the entire result set of the {@link Cursor}. *

* * @return The zero-based start position. */ public @IntRange(from = 0) int getStartPosition() { return mStartPos; } /** * Sets the start position of this cursor window. *

* The start position is the zero-based index of the first row that this window contains * relative to the entire result set of the {@link Cursor}. *

* * @param pos The new zero-based start position. */ public void setStartPosition(@IntRange(from = 0) int pos) { mStartPos = pos; } /** * Gets the number of rows in this window. * * @return The number of rows in this cursor window. */ public @IntRange(from = 0) int getNumRows() { return rows.size(); } /** * Sets the number of columns in this window. *

* This method must be called before any rows are added to the window, otherwise * it will fail to set the number of columns if it differs from the current number * of columns. *

* * @param columnNum The new number of columns. * @return True if successful. */ public boolean setNumColumns(@IntRange(from = 0) int columnNum) { this.numColumns = columnNum; return true; } /** * Allocates a new row at the end of this cursor window. * * @return True if successful, false if the cursor window is out of memory. */ public boolean allocRow() { rows.add(new Row(numColumns)); return true; } /** * Frees the last row in this cursor window. */ public void freeLastRow(){ rows.remove(rows.size()-1); } /** * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_NULL}. * * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}. * @deprecated Use {@link #getType(int, int)} instead. */ @Deprecated public boolean isNull(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return getType(row, column) == Cursor.FIELD_TYPE_NULL; } /** * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}. * * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or * {@link Cursor#FIELD_TYPE_NULL}. * @deprecated Use {@link #getType(int, int)} instead. */ @Deprecated public boolean isBlob(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { int type = getType(row, column); return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL; } /** * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_INTEGER}. * * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}. * @deprecated Use {@link #getType(int, int)} instead. */ @Deprecated public boolean isLong(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return getType(row, column) == Cursor.FIELD_TYPE_INTEGER; } /** * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_FLOAT}. * * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}. * @deprecated Use {@link #getType(int, int)} instead. */ @Deprecated public boolean isFloat(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return getType(row, column) == Cursor.FIELD_TYPE_FLOAT; } /** * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}. * * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING} * or {@link Cursor#FIELD_TYPE_NULL}. * @deprecated Use {@link #getType(int, int)} instead. */ @Deprecated public boolean isString(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { int type = getType(row, column); return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL; } private Value value(int rowN, int colN) { Row row = rows.get(rowN); if (row == null) { throw new IllegalArgumentException("Bad row number: " + rowN + ", count: " + rows.size()); } return row.get(colN); } private boolean putValue(Value value, int rowN, int colN) { return rows.get(rowN).set(colN, value); } /** * Returns the type of the field at the specified row and column index. * * @param row The zero-based row index. * @param column The zero-based column index. * @return The field type. */ public @Cursor.FieldType int getType(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return value(row - mStartPos, column).type; } /** * Gets the value of the field at the specified row and column index as a byte array. *

* The result is determined as follows: *

    *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result * is null.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result * is the blob value.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result * is the array of bytes that make up the internal representation of the * string value.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.
  • *
*

* * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a byte array. */ public byte[] getBlob(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { Value value = value(row - mStartPos, column); switch (value.type) { case Cursor.FIELD_TYPE_NULL: return null; case Cursor.FIELD_TYPE_BLOB: // This matches Android's behavior, which does not match the SQLite spec byte[] blob = (byte[])value.value; return blob == null ? new byte[]{} : blob; case Cursor.FIELD_TYPE_STRING: // Matches the Android behavior to contain a zero-byte at the end byte[] stringBytes = ((String) value.value).getBytes(UTF_8); return Arrays.copyOf(stringBytes, stringBytes.length + 1); default: throw new android.database.sqlite.SQLiteException( "Getting blob when column is non-blob. Row " + row + ", col " + column); } } /** * Gets the value of the field at the specified row and column index as a string. *

* The result is determined as follows: *

    *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result * is null.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result * is the string value.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result * is a string representation of the integer in decimal, obtained by formatting the * value with the printf family of functions using * format specifier %lld.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result * is a string representation of the floating-point value in decimal, obtained by * formatting the value with the printf family of functions using * format specifier %g.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a * {@link SQLiteException} is thrown.
  • *
*

* * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a string. */ public String getString(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { Value val = value(row - mStartPos, column); if (val.type == Cursor.FIELD_TYPE_BLOB) { throw new android.database.sqlite.SQLiteException( "Getting string when column is blob. Row " + row + ", col " + column); } Object value = val.value; return value == null ? null : String.valueOf(value); } /** * Copies the text of the field at the specified row and column index into * a {@link CharArrayBuffer}. *

* The buffer is populated as follows: *

    *
  • If the buffer is too small for the value to be copied, then it is * automatically resized.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer * is set to an empty string.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer * is set to the contents of the string.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer * is set to a string representation of the integer in decimal, obtained by formatting the * value with the printf family of functions using * format specifier %lld.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is * set to a string representation of the floating-point value in decimal, obtained by * formatting the value with the printf family of functions using * format specifier %g.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a * {@link SQLiteException} is thrown.
  • *
*

* * @param row The zero-based row index. * @param column The zero-based column index. * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically * resized if the requested string is larger than the buffer's current capacity. */ // public void copyStringToBuffer(@IntRange(from = 0) int row, @IntRange(from = 0) int column, // CharArrayBuffer buffer) { // if (buffer == null) { // throw new IllegalArgumentException("CharArrayBuffer should not be null"); // } // acquireReference(); // try { // nativeCopyStringToBuffer(mWindowPtr, row - mStartPos, column, buffer); // } finally { // releaseReference(); // } // } private Number getNumber(int row, int column) { Value value = value(row, column); switch (value.type) { case Cursor.FIELD_TYPE_NULL: case Codes.SQLITE_NULL: return 0; case Cursor.FIELD_TYPE_INTEGER: case Cursor.FIELD_TYPE_FLOAT: return (Number) value.value; case Cursor.FIELD_TYPE_STRING: { try { return Double.parseDouble((String) value.value); } catch (NumberFormatException e) { return 0; } } case Cursor.FIELD_TYPE_BLOB: throw new android.database.sqlite.SQLiteException("could not convert "+value); default: throw new android.database.sqlite.SQLiteException("unknown type: "+value.type); } } /** * Gets the value of the field at the specified row and column index as a long. *

* The result is determined as follows: *

    *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result * is 0L.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result * is the value obtained by parsing the string value with strtoll. *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result * is the long value.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result * is the floating-point value converted to a long.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a * {@link SQLiteException} is thrown.
  • *
*

* * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a long. */ public long getLong(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return getNumber(row - mStartPos, column).longValue(); } /** * Gets the value of the field at the specified row and column index as a * double. *

* The result is determined as follows: *

    *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result * is 0.0.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result * is the value obtained by parsing the string value with strtod. *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result * is the integer value converted to a double.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result * is the double value.
  • *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a * {@link SQLiteException} is thrown.
  • *
*

* * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a double. */ public double getDouble(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return getNumber(row - mStartPos, column).doubleValue(); } /** * Gets the value of the field at the specified row and column index as a * short. *

* The result is determined by invoking {@link #getLong} and converting the * result to short. *

* * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a short. */ public short getShort(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return (short) getLong(row, column); } /** * Gets the value of the field at the specified row and column index as an * int. *

* The result is determined by invoking {@link #getLong} and converting the * result to int. *

* * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as an int. */ public int getInt(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return (int) getLong(row, column); } /** * Gets the value of the field at the specified row and column index as a * float. *

* The result is determined by invoking {@link #getDouble} and converting the * result to float. *

* * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as an float. */ public float getFloat(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return (float) getDouble(row, column); } /** * Copies a byte array into the field at the specified row and column index. * * @param value The value to store. * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ public boolean putBlob(byte[] value, @IntRange(from = 0) int row, @IntRange(from = 0) int column) { // Real Android will crash in native code if putString is called with a null value. Preconditions.checkNotNull(value); return putValue(new Value(value, Cursor.FIELD_TYPE_BLOB), row, column); } /** * Copies a string into the field at the specified row and column index. * * @param value The value to store. * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ public boolean putString(String value, @IntRange(from = 0) int row, @IntRange(from = 0) int column) { // Real Android will crash in native code if putString is called with a null value. Preconditions.checkNotNull(value); return putValue(new Value(value, Cursor.FIELD_TYPE_STRING), row, column); } /** * Puts a long integer into the field at the specified row and column index. * * @param value The value to store. * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ public boolean putLong(long value, @IntRange(from = 0) int row, @IntRange(from = 0) int column) { return putValue(new Value(value, Cursor.FIELD_TYPE_INTEGER), row, column); } /** * Puts a double-precision floating point value into the field at the * specified row and column index. * * @param value The value to store. * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ public boolean putDouble(double value, @IntRange(from = 0) int row, @IntRange(from = 0) int column) { return putValue(new Value(value, Cursor.FIELD_TYPE_FLOAT), row, column); } /** * Puts a null value into the field at the specified row and column index. * * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ public boolean putNull(@IntRange(from = 0) int row, @IntRange(from = 0) int column) { return putValue(new Value(null, Cursor.FIELD_TYPE_NULL), row, column); } // public static final @android.annotation.NonNull Parcelable.Creator CREATOR // = new Parcelable.Creator() { // public CursorWindow createFromParcel(Parcel source) { // return new CursorWindow(source); // } // // public CursorWindow[] newArray(int size) { // return new CursorWindow[size]; // } // }; // public static CursorWindow newFromParcel(Parcel p) { // return CREATOR.createFromParcel(p); // } public int describeContents() { return 0; } // public void writeToParcel(Parcel dest, int flags) { // acquireReference(); // try { // dest.writeInt(mStartPos); // nativeWriteToParcel(mWindowPtr, dest); // } finally { // releaseReference(); // } // // if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { // releaseReference(); // } // } @Override protected void onAllReferencesReleased() { dispose(); } private static int getCursorWindowSize() { if (sCursorWindowSize < 0) { // The cursor window size. resource xml file specifies the value in kB. // convert it to bytes here by multiplying with 1024. sCursorWindowSize = 2024 * 1024; } return sCursorWindowSize; } @Override public String toString() { return getName(); } private static class Row { private final List values; public Row(int length) { values = new ArrayList(length); for (int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy