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