The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2007 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package android.database; |
| 18 | |
Artur Satayev | 2695800 | 2019-12-10 17:47:52 +0000 | [diff] [blame^] | 19 | import android.compat.annotation.UnsupportedAppUsage; |
Mathew Inwood | 31755f9 | 2018-12-20 13:53:36 +0000 | [diff] [blame] | 20 | import android.os.Build; |
Anton Hansson | 788ec75 | 2019-04-30 15:21:24 +0100 | [diff] [blame] | 21 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 22 | import java.util.ArrayList; |
| 23 | |
| 24 | /** |
| 25 | * A mutable cursor implementation backed by an array of {@code Object}s. Use |
| 26 | * {@link #newRow()} to add rows. Automatically expands internal capacity |
| 27 | * as needed. |
| 28 | */ |
| 29 | public class MatrixCursor extends AbstractCursor { |
| 30 | |
| 31 | private final String[] columnNames; |
Mathew Inwood | 31755f9 | 2018-12-20 13:53:36 +0000 | [diff] [blame] | 32 | @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 33 | private Object[] data; |
Mathew Inwood | 31755f9 | 2018-12-20 13:53:36 +0000 | [diff] [blame] | 34 | @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 35 | private int rowCount = 0; |
| 36 | private final int columnCount; |
| 37 | |
| 38 | /** |
| 39 | * Constructs a new cursor with the given initial capacity. |
| 40 | * |
| 41 | * @param columnNames names of the columns, the ordering of which |
| 42 | * determines column ordering elsewhere in this cursor |
| 43 | * @param initialCapacity in rows |
| 44 | */ |
| 45 | public MatrixCursor(String[] columnNames, int initialCapacity) { |
| 46 | this.columnNames = columnNames; |
| 47 | this.columnCount = columnNames.length; |
| 48 | |
| 49 | if (initialCapacity < 1) { |
| 50 | initialCapacity = 1; |
| 51 | } |
| 52 | |
| 53 | this.data = new Object[columnCount * initialCapacity]; |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * Constructs a new cursor. |
| 58 | * |
| 59 | * @param columnNames names of the columns, the ordering of which |
| 60 | * determines column ordering elsewhere in this cursor |
| 61 | */ |
| 62 | public MatrixCursor(String[] columnNames) { |
| 63 | this(columnNames, 16); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Gets value at the given column for the current row. |
| 68 | */ |
Mathew Inwood | 41b3194 | 2018-08-10 16:00:53 +0100 | [diff] [blame] | 69 | @UnsupportedAppUsage |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 70 | private Object get(int column) { |
| 71 | if (column < 0 || column >= columnCount) { |
| 72 | throw new CursorIndexOutOfBoundsException("Requested column: " |
| 73 | + column + ", # of columns: " + columnCount); |
| 74 | } |
| 75 | if (mPos < 0) { |
| 76 | throw new CursorIndexOutOfBoundsException("Before first row."); |
| 77 | } |
| 78 | if (mPos >= rowCount) { |
| 79 | throw new CursorIndexOutOfBoundsException("After last row."); |
| 80 | } |
| 81 | return data[mPos * columnCount + column]; |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Adds a new row to the end and returns a builder for that row. Not safe |
| 86 | * for concurrent use. |
| 87 | * |
| 88 | * @return builder which can be used to set the column values for the new |
| 89 | * row |
| 90 | */ |
| 91 | public RowBuilder newRow() { |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 92 | final int row = rowCount++; |
| 93 | final int endIndex = rowCount * columnCount; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 94 | ensureCapacity(endIndex); |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 95 | return new RowBuilder(row); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Adds a new row to the end with the given column values. Not safe |
| 100 | * for concurrent use. |
| 101 | * |
| 102 | * @throws IllegalArgumentException if {@code columnValues.length != |
| 103 | * columnNames.length} |
| 104 | * @param columnValues in the same order as the the column names specified |
| 105 | * at cursor construction time |
| 106 | */ |
| 107 | public void addRow(Object[] columnValues) { |
| 108 | if (columnValues.length != columnCount) { |
| 109 | throw new IllegalArgumentException("columnNames.length = " |
| 110 | + columnCount + ", columnValues.length = " |
| 111 | + columnValues.length); |
| 112 | } |
| 113 | |
| 114 | int start = rowCount++ * columnCount; |
| 115 | ensureCapacity(start + columnCount); |
| 116 | System.arraycopy(columnValues, 0, data, start, columnCount); |
| 117 | } |
| 118 | |
| 119 | /** |
| 120 | * Adds a new row to the end with the given column values. Not safe |
| 121 | * for concurrent use. |
| 122 | * |
| 123 | * @throws IllegalArgumentException if {@code columnValues.size() != |
| 124 | * columnNames.length} |
| 125 | * @param columnValues in the same order as the the column names specified |
| 126 | * at cursor construction time |
| 127 | */ |
| 128 | public void addRow(Iterable<?> columnValues) { |
| 129 | int start = rowCount * columnCount; |
| 130 | int end = start + columnCount; |
| 131 | ensureCapacity(end); |
| 132 | |
| 133 | if (columnValues instanceof ArrayList<?>) { |
| 134 | addRow((ArrayList<?>) columnValues, start); |
| 135 | return; |
| 136 | } |
| 137 | |
| 138 | int current = start; |
| 139 | Object[] localData = data; |
| 140 | for (Object columnValue : columnValues) { |
| 141 | if (current == end) { |
| 142 | // TODO: null out row? |
| 143 | throw new IllegalArgumentException( |
| 144 | "columnValues.size() > columnNames.length"); |
| 145 | } |
| 146 | localData[current++] = columnValue; |
| 147 | } |
| 148 | |
| 149 | if (current != end) { |
| 150 | // TODO: null out row? |
| 151 | throw new IllegalArgumentException( |
| 152 | "columnValues.size() < columnNames.length"); |
| 153 | } |
| 154 | |
| 155 | // Increase row count here in case we encounter an exception. |
| 156 | rowCount++; |
| 157 | } |
| 158 | |
| 159 | /** Optimization for {@link ArrayList}. */ |
| 160 | private void addRow(ArrayList<?> columnValues, int start) { |
| 161 | int size = columnValues.size(); |
| 162 | if (size != columnCount) { |
| 163 | throw new IllegalArgumentException("columnNames.length = " |
| 164 | + columnCount + ", columnValues.size() = " + size); |
| 165 | } |
| 166 | |
| 167 | rowCount++; |
| 168 | Object[] localData = data; |
| 169 | for (int i = 0; i < size; i++) { |
| 170 | localData[start + i] = columnValues.get(i); |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | /** Ensures that this cursor has enough capacity. */ |
| 175 | private void ensureCapacity(int size) { |
| 176 | if (size > data.length) { |
| 177 | Object[] oldData = this.data; |
| 178 | int newSize = data.length * 2; |
| 179 | if (newSize < size) { |
| 180 | newSize = size; |
| 181 | } |
| 182 | this.data = new Object[newSize]; |
| 183 | System.arraycopy(oldData, 0, this.data, 0, oldData.length); |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | /** |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 188 | * Builds a row of values using either of these approaches: |
| 189 | * <ul> |
| 190 | * <li>Values can be added with explicit column ordering using |
| 191 | * {@link #add(Object)}, which starts from the left-most column and adds one |
| 192 | * column value at a time. This follows the same ordering as the column |
| 193 | * names specified at cursor construction time. |
| 194 | * <li>Column and value pairs can be offered for possible inclusion using |
Jeff Sharkey | b7757a6 | 2013-09-09 17:46:54 -0700 | [diff] [blame] | 195 | * {@link #add(String, Object)}. If the cursor includes the given column, |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 196 | * the value will be set for that column, otherwise the value is ignored. |
| 197 | * This approach is useful when matching data to a custom projection. |
| 198 | * </ul> |
| 199 | * Undefined values are left as {@code null}. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 200 | */ |
| 201 | public class RowBuilder { |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 202 | private final int row; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 203 | private final int endIndex; |
| 204 | |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 205 | private int index; |
| 206 | |
| 207 | RowBuilder(int row) { |
| 208 | this.row = row; |
| 209 | this.index = row * columnCount; |
| 210 | this.endIndex = index + columnCount; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Sets the next column value in this row. |
| 215 | * |
| 216 | * @throws CursorIndexOutOfBoundsException if you try to add too many |
| 217 | * values |
| 218 | * @return this builder to support chaining |
| 219 | */ |
| 220 | public RowBuilder add(Object columnValue) { |
| 221 | if (index == endIndex) { |
| 222 | throw new CursorIndexOutOfBoundsException( |
| 223 | "No more columns left."); |
| 224 | } |
| 225 | |
| 226 | data[index++] = columnValue; |
| 227 | return this; |
| 228 | } |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 229 | |
| 230 | /** |
| 231 | * Offer value for possible inclusion if this cursor defines the given |
| 232 | * column. Columns not defined by the cursor are silently ignored. |
| 233 | * |
| 234 | * @return this builder to support chaining |
| 235 | */ |
Jeff Sharkey | b7757a6 | 2013-09-09 17:46:54 -0700 | [diff] [blame] | 236 | public RowBuilder add(String columnName, Object value) { |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 237 | for (int i = 0; i < columnNames.length; i++) { |
| 238 | if (columnName.equals(columnNames[i])) { |
| 239 | data[(row * columnCount) + i] = value; |
| 240 | } |
| 241 | } |
| 242 | return this; |
| 243 | } |
Anton Hansson | 788ec75 | 2019-04-30 15:21:24 +0100 | [diff] [blame] | 244 | |
| 245 | /** @hide */ |
| 246 | public final RowBuilder add(int columnIndex, Object value) { |
| 247 | data[(row * columnCount) + columnIndex] = value; |
| 248 | return this; |
| 249 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 250 | } |
| 251 | |
| 252 | // AbstractCursor implementation. |
| 253 | |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 254 | @Override |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 255 | public int getCount() { |
| 256 | return rowCount; |
| 257 | } |
| 258 | |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 259 | @Override |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 260 | public String[] getColumnNames() { |
| 261 | return columnNames; |
| 262 | } |
| 263 | |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 264 | @Override |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 265 | public String getString(int column) { |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 266 | Object value = get(column); |
| 267 | if (value == null) return null; |
| 268 | return value.toString(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 269 | } |
| 270 | |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 271 | @Override |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 272 | public short getShort(int column) { |
| 273 | Object value = get(column); |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 274 | if (value == null) return 0; |
| 275 | if (value instanceof Number) return ((Number) value).shortValue(); |
| 276 | return Short.parseShort(value.toString()); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 277 | } |
| 278 | |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 279 | @Override |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 280 | public int getInt(int column) { |
| 281 | Object value = get(column); |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 282 | if (value == null) return 0; |
| 283 | if (value instanceof Number) return ((Number) value).intValue(); |
| 284 | return Integer.parseInt(value.toString()); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 285 | } |
| 286 | |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 287 | @Override |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 288 | public long getLong(int column) { |
| 289 | Object value = get(column); |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 290 | if (value == null) return 0; |
| 291 | if (value instanceof Number) return ((Number) value).longValue(); |
| 292 | return Long.parseLong(value.toString()); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 293 | } |
| 294 | |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 295 | @Override |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 296 | public float getFloat(int column) { |
| 297 | Object value = get(column); |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 298 | if (value == null) return 0.0f; |
| 299 | if (value instanceof Number) return ((Number) value).floatValue(); |
| 300 | return Float.parseFloat(value.toString()); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 301 | } |
| 302 | |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 303 | @Override |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 304 | public double getDouble(int column) { |
| 305 | Object value = get(column); |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 306 | if (value == null) return 0.0d; |
| 307 | if (value instanceof Number) return ((Number) value).doubleValue(); |
| 308 | return Double.parseDouble(value.toString()); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 309 | } |
| 310 | |
Bjorn Bringert | f2cec58 | 2009-07-22 20:40:04 +0100 | [diff] [blame] | 311 | @Override |
Todd Kennedy | 783123f | 2011-07-01 14:19:03 -0700 | [diff] [blame] | 312 | public byte[] getBlob(int column) { |
| 313 | Object value = get(column); |
| 314 | return (byte[]) value; |
| 315 | } |
| 316 | |
| 317 | @Override |
Vasu Nori | 8b0dd7d | 2010-05-18 11:54:31 -0700 | [diff] [blame] | 318 | public int getType(int column) { |
| 319 | return DatabaseUtils.getTypeOfObject(get(column)); |
| 320 | } |
| 321 | |
| 322 | @Override |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 323 | public boolean isNull(int column) { |
| 324 | return get(column) == null; |
| 325 | } |
| 326 | } |