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 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.os.Build; 21 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 @android.ravenwood.annotation.RavenwoodKeepWholeClass 30 public class MatrixCursor extends AbstractCursor { 31 32 private final String[] columnNames; 33 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 34 private Object[] data; 35 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 36 private int rowCount = 0; 37 private final int columnCount; 38 39 /** 40 * Constructs a new cursor with the given initial capacity. 41 * 42 * @param columnNames names of the columns, the ordering of which 43 * determines column ordering elsewhere in this cursor 44 * @param initialCapacity in rows 45 */ MatrixCursor(String[] columnNames, int initialCapacity)46 public MatrixCursor(String[] columnNames, int initialCapacity) { 47 this.columnNames = columnNames; 48 this.columnCount = columnNames.length; 49 50 if (initialCapacity < 1) { 51 initialCapacity = 1; 52 } 53 54 this.data = new Object[columnCount * initialCapacity]; 55 } 56 57 /** 58 * Constructs a new cursor. 59 * 60 * @param columnNames names of the columns, the ordering of which 61 * determines column ordering elsewhere in this cursor 62 */ MatrixCursor(String[] columnNames)63 public MatrixCursor(String[] columnNames) { 64 this(columnNames, 16); 65 } 66 67 /** 68 * Gets value at the given column for the current row. 69 */ 70 @UnsupportedAppUsage get(int column)71 private Object get(int column) { 72 if (column < 0 || column >= columnCount) { 73 throw new CursorIndexOutOfBoundsException("Requested column: " 74 + column + ", # of columns: " + columnCount); 75 } 76 if (mPos < 0) { 77 throw new CursorIndexOutOfBoundsException("Before first row."); 78 } 79 if (mPos >= rowCount) { 80 throw new CursorIndexOutOfBoundsException("After last row."); 81 } 82 return data[mPos * columnCount + column]; 83 } 84 85 /** 86 * Adds a new row to the end and returns a builder for that row. Not safe 87 * for concurrent use. 88 * 89 * @return builder which can be used to set the column values for the new 90 * row 91 */ newRow()92 public RowBuilder newRow() { 93 final int row = rowCount++; 94 final int endIndex = rowCount * columnCount; 95 ensureCapacity(endIndex); 96 return new RowBuilder(row); 97 } 98 99 /** 100 * Adds a new row to the end with the given column values. Not safe 101 * for concurrent use. 102 * 103 * @throws IllegalArgumentException if {@code columnValues.length != 104 * columnNames.length} 105 * @param columnValues in the same order as the the column names specified 106 * at cursor construction time 107 */ addRow(Object[] columnValues)108 public void addRow(Object[] columnValues) { 109 if (columnValues.length != columnCount) { 110 throw new IllegalArgumentException("columnNames.length = " 111 + columnCount + ", columnValues.length = " 112 + columnValues.length); 113 } 114 115 int start = rowCount++ * columnCount; 116 ensureCapacity(start + columnCount); 117 System.arraycopy(columnValues, 0, data, start, columnCount); 118 } 119 120 /** 121 * Adds a new row to the end with the given column values. Not safe 122 * for concurrent use. 123 * 124 * @throws IllegalArgumentException if {@code columnValues.size() != 125 * columnNames.length} 126 * @param columnValues in the same order as the the column names specified 127 * at cursor construction time 128 */ addRow(Iterable<?> columnValues)129 public void addRow(Iterable<?> columnValues) { 130 int start = rowCount * columnCount; 131 int end = start + columnCount; 132 ensureCapacity(end); 133 134 if (columnValues instanceof ArrayList<?>) { 135 addRow((ArrayList<?>) columnValues, start); 136 return; 137 } 138 139 int current = start; 140 Object[] localData = data; 141 for (Object columnValue : columnValues) { 142 if (current == end) { 143 // TODO: null out row? 144 throw new IllegalArgumentException( 145 "columnValues.size() > columnNames.length"); 146 } 147 localData[current++] = columnValue; 148 } 149 150 if (current != end) { 151 // TODO: null out row? 152 throw new IllegalArgumentException( 153 "columnValues.size() < columnNames.length"); 154 } 155 156 // Increase row count here in case we encounter an exception. 157 rowCount++; 158 } 159 160 /** Optimization for {@link ArrayList}. */ addRow(ArrayList<?> columnValues, int start)161 private void addRow(ArrayList<?> columnValues, int start) { 162 int size = columnValues.size(); 163 if (size != columnCount) { 164 throw new IllegalArgumentException("columnNames.length = " 165 + columnCount + ", columnValues.size() = " + size); 166 } 167 168 rowCount++; 169 Object[] localData = data; 170 for (int i = 0; i < size; i++) { 171 localData[start + i] = columnValues.get(i); 172 } 173 } 174 175 /** Ensures that this cursor has enough capacity. */ ensureCapacity(int size)176 private void ensureCapacity(int size) { 177 if (size > data.length) { 178 Object[] oldData = this.data; 179 int newSize = data.length * 2; 180 if (newSize < size) { 181 newSize = size; 182 } 183 this.data = new Object[newSize]; 184 System.arraycopy(oldData, 0, this.data, 0, oldData.length); 185 } 186 } 187 188 /** 189 * Builds a row of values using either of these approaches: 190 * <ul> 191 * <li>Values can be added with explicit column ordering using 192 * {@link #add(Object)}, which starts from the left-most column and adds one 193 * column value at a time. This follows the same ordering as the column 194 * names specified at cursor construction time. 195 * <li>Column and value pairs can be offered for possible inclusion using 196 * {@link #add(String, Object)}. If the cursor includes the given column, 197 * the value will be set for that column, otherwise the value is ignored. 198 * This approach is useful when matching data to a custom projection. 199 * </ul> 200 * Undefined values are left as {@code null}. 201 */ 202 public class RowBuilder { 203 private final int row; 204 private final int endIndex; 205 206 private int index; 207 RowBuilder(int row)208 RowBuilder(int row) { 209 this.row = row; 210 this.index = row * columnCount; 211 this.endIndex = index + columnCount; 212 } 213 214 /** 215 * Sets the next column value in this row. 216 * 217 * @throws CursorIndexOutOfBoundsException if you try to add too many 218 * values 219 * @return this builder to support chaining 220 */ add(Object columnValue)221 public RowBuilder add(Object columnValue) { 222 if (index == endIndex) { 223 throw new CursorIndexOutOfBoundsException( 224 "No more columns left."); 225 } 226 227 data[index++] = columnValue; 228 return this; 229 } 230 231 /** 232 * Offer value for possible inclusion if this cursor defines the given 233 * column. Columns not defined by the cursor are silently ignored. 234 * 235 * @return this builder to support chaining 236 */ add(String columnName, Object value)237 public RowBuilder add(String columnName, Object value) { 238 for (int i = 0; i < columnNames.length; i++) { 239 if (columnName.equals(columnNames[i])) { 240 data[(row * columnCount) + i] = value; 241 } 242 } 243 return this; 244 } 245 246 /** @hide */ add(int columnIndex, Object value)247 public final RowBuilder add(int columnIndex, Object value) { 248 data[(row * columnCount) + columnIndex] = value; 249 return this; 250 } 251 } 252 253 // AbstractCursor implementation. 254 255 @Override getCount()256 public int getCount() { 257 return rowCount; 258 } 259 260 @Override getColumnNames()261 public String[] getColumnNames() { 262 return columnNames; 263 } 264 265 @Override getString(int column)266 public String getString(int column) { 267 Object value = get(column); 268 if (value == null) return null; 269 return value.toString(); 270 } 271 272 @Override getShort(int column)273 public short getShort(int column) { 274 Object value = get(column); 275 if (value == null) return 0; 276 if (value instanceof Number) return ((Number) value).shortValue(); 277 return Short.parseShort(value.toString()); 278 } 279 280 @Override getInt(int column)281 public int getInt(int column) { 282 Object value = get(column); 283 if (value == null) return 0; 284 if (value instanceof Number) return ((Number) value).intValue(); 285 return Integer.parseInt(value.toString()); 286 } 287 288 @Override getLong(int column)289 public long getLong(int column) { 290 Object value = get(column); 291 if (value == null) return 0; 292 if (value instanceof Number) return ((Number) value).longValue(); 293 return Long.parseLong(value.toString()); 294 } 295 296 @Override getFloat(int column)297 public float getFloat(int column) { 298 Object value = get(column); 299 if (value == null) return 0.0f; 300 if (value instanceof Number) return ((Number) value).floatValue(); 301 return Float.parseFloat(value.toString()); 302 } 303 304 @Override getDouble(int column)305 public double getDouble(int column) { 306 Object value = get(column); 307 if (value == null) return 0.0d; 308 if (value instanceof Number) return ((Number) value).doubleValue(); 309 return Double.parseDouble(value.toString()); 310 } 311 312 @Override getBlob(int column)313 public byte[] getBlob(int column) { 314 Object value = get(column); 315 return (byte[]) value; 316 } 317 318 @Override getType(int column)319 public int getType(int column) { 320 return DatabaseUtils.getTypeOfObject(get(column)); 321 } 322 323 @Override isNull(int column)324 public boolean isNull(int column) { 325 return get(column) == null; 326 } 327 } 328