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.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 public class MatrixCursor extends AbstractCursor { 30 31 private final String[] columnNames; 32 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 33 private Object[] data; 34 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 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 */ MatrixCursor(String[] columnNames, int initialCapacity)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 */ MatrixCursor(String[] columnNames)62 public MatrixCursor(String[] columnNames) { 63 this(columnNames, 16); 64 } 65 66 /** 67 * Gets value at the given column for the current row. 68 */ 69 @UnsupportedAppUsage get(int column)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 */ newRow()91 public RowBuilder newRow() { 92 final int row = rowCount++; 93 final int endIndex = rowCount * columnCount; 94 ensureCapacity(endIndex); 95 return new RowBuilder(row); 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 */ addRow(Object[] columnValues)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 */ addRow(Iterable<?> columnValues)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}. */ addRow(ArrayList<?> columnValues, int start)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. */ ensureCapacity(int size)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 /** 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 195 * {@link #add(String, Object)}. If the cursor includes the given column, 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}. 200 */ 201 public class RowBuilder { 202 private final int row; 203 private final int endIndex; 204 205 private int index; 206 RowBuilder(int row)207 RowBuilder(int row) { 208 this.row = row; 209 this.index = row * columnCount; 210 this.endIndex = index + columnCount; 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 */ add(Object columnValue)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 } 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 */ add(String columnName, Object value)236 public RowBuilder add(String columnName, Object value) { 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 } 244 245 /** @hide */ add(int columnIndex, Object value)246 public final RowBuilder add(int columnIndex, Object value) { 247 data[(row * columnCount) + columnIndex] = value; 248 return this; 249 } 250 } 251 252 // AbstractCursor implementation. 253 254 @Override getCount()255 public int getCount() { 256 return rowCount; 257 } 258 259 @Override getColumnNames()260 public String[] getColumnNames() { 261 return columnNames; 262 } 263 264 @Override getString(int column)265 public String getString(int column) { 266 Object value = get(column); 267 if (value == null) return null; 268 return value.toString(); 269 } 270 271 @Override getShort(int column)272 public short getShort(int column) { 273 Object value = get(column); 274 if (value == null) return 0; 275 if (value instanceof Number) return ((Number) value).shortValue(); 276 return Short.parseShort(value.toString()); 277 } 278 279 @Override getInt(int column)280 public int getInt(int column) { 281 Object value = get(column); 282 if (value == null) return 0; 283 if (value instanceof Number) return ((Number) value).intValue(); 284 return Integer.parseInt(value.toString()); 285 } 286 287 @Override getLong(int column)288 public long getLong(int column) { 289 Object value = get(column); 290 if (value == null) return 0; 291 if (value instanceof Number) return ((Number) value).longValue(); 292 return Long.parseLong(value.toString()); 293 } 294 295 @Override getFloat(int column)296 public float getFloat(int column) { 297 Object value = get(column); 298 if (value == null) return 0.0f; 299 if (value instanceof Number) return ((Number) value).floatValue(); 300 return Float.parseFloat(value.toString()); 301 } 302 303 @Override getDouble(int column)304 public double getDouble(int column) { 305 Object value = get(column); 306 if (value == null) return 0.0d; 307 if (value instanceof Number) return ((Number) value).doubleValue(); 308 return Double.parseDouble(value.toString()); 309 } 310 311 @Override getBlob(int column)312 public byte[] getBlob(int column) { 313 Object value = get(column); 314 return (byte[]) value; 315 } 316 317 @Override getType(int column)318 public int getType(int column) { 319 return DatabaseUtils.getTypeOfObject(get(column)); 320 } 321 322 @Override isNull(int column)323 public boolean isNull(int column) { 324 return get(column) == null; 325 } 326 } 327