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 */ MatrixCursor(String[] columnNames, int initialCapacity)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 */ MatrixCursor(String[] columnNames)57 public MatrixCursor(String[] columnNames) { 58 this(columnNames, 16); 59 } 60 61 /** 62 * Gets value at the given column for the current row. 63 */ get(int column)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 */ newRow()85 public RowBuilder newRow() { 86 rowCount++; 87 int endIndex = rowCount * columnCount; 88 ensureCapacity(endIndex); 89 int start = endIndex - columnCount; 90 return new RowBuilder(start, endIndex); 91 } 92 93 /** 94 * Adds a new row to the end with the given column values. Not safe 95 * for concurrent use. 96 * 97 * @throws IllegalArgumentException if {@code columnValues.length != 98 * columnNames.length} 99 * @param columnValues in the same order as the the column names specified 100 * at cursor construction time 101 */ addRow(Object[] columnValues)102 public void addRow(Object[] columnValues) { 103 if (columnValues.length != columnCount) { 104 throw new IllegalArgumentException("columnNames.length = " 105 + columnCount + ", columnValues.length = " 106 + columnValues.length); 107 } 108 109 int start = rowCount++ * columnCount; 110 ensureCapacity(start + columnCount); 111 System.arraycopy(columnValues, 0, data, start, columnCount); 112 } 113 114 /** 115 * Adds a new row to the end with the given column values. Not safe 116 * for concurrent use. 117 * 118 * @throws IllegalArgumentException if {@code columnValues.size() != 119 * columnNames.length} 120 * @param columnValues in the same order as the the column names specified 121 * at cursor construction time 122 */ addRow(Iterable<?> columnValues)123 public void addRow(Iterable<?> columnValues) { 124 int start = rowCount * columnCount; 125 int end = start + columnCount; 126 ensureCapacity(end); 127 128 if (columnValues instanceof ArrayList<?>) { 129 addRow((ArrayList<?>) columnValues, start); 130 return; 131 } 132 133 int current = start; 134 Object[] localData = data; 135 for (Object columnValue : columnValues) { 136 if (current == end) { 137 // TODO: null out row? 138 throw new IllegalArgumentException( 139 "columnValues.size() > columnNames.length"); 140 } 141 localData[current++] = columnValue; 142 } 143 144 if (current != end) { 145 // TODO: null out row? 146 throw new IllegalArgumentException( 147 "columnValues.size() < columnNames.length"); 148 } 149 150 // Increase row count here in case we encounter an exception. 151 rowCount++; 152 } 153 154 /** Optimization for {@link ArrayList}. */ addRow(ArrayList<?> columnValues, int start)155 private void addRow(ArrayList<?> columnValues, int start) { 156 int size = columnValues.size(); 157 if (size != columnCount) { 158 throw new IllegalArgumentException("columnNames.length = " 159 + columnCount + ", columnValues.size() = " + size); 160 } 161 162 rowCount++; 163 Object[] localData = data; 164 for (int i = 0; i < size; i++) { 165 localData[start + i] = columnValues.get(i); 166 } 167 } 168 169 /** Ensures that this cursor has enough capacity. */ ensureCapacity(int size)170 private void ensureCapacity(int size) { 171 if (size > data.length) { 172 Object[] oldData = this.data; 173 int newSize = data.length * 2; 174 if (newSize < size) { 175 newSize = size; 176 } 177 this.data = new Object[newSize]; 178 System.arraycopy(oldData, 0, this.data, 0, oldData.length); 179 } 180 } 181 182 /** 183 * Builds a row, starting from the left-most column and adding one column 184 * value at a time. Follows the same ordering as the column names specified 185 * at cursor construction time. 186 */ 187 public class RowBuilder { 188 189 private int index; 190 private final int endIndex; 191 RowBuilder(int index, int endIndex)192 RowBuilder(int index, int endIndex) { 193 this.index = index; 194 this.endIndex = endIndex; 195 } 196 197 /** 198 * Sets the next column value in this row. 199 * 200 * @throws CursorIndexOutOfBoundsException if you try to add too many 201 * values 202 * @return this builder to support chaining 203 */ add(Object columnValue)204 public RowBuilder add(Object columnValue) { 205 if (index == endIndex) { 206 throw new CursorIndexOutOfBoundsException( 207 "No more columns left."); 208 } 209 210 data[index++] = columnValue; 211 return this; 212 } 213 } 214 215 // AbstractCursor implementation. 216 217 @Override getCount()218 public int getCount() { 219 return rowCount; 220 } 221 222 @Override getColumnNames()223 public String[] getColumnNames() { 224 return columnNames; 225 } 226 227 @Override getString(int column)228 public String getString(int column) { 229 Object value = get(column); 230 if (value == null) return null; 231 return value.toString(); 232 } 233 234 @Override getShort(int column)235 public short getShort(int column) { 236 Object value = get(column); 237 if (value == null) return 0; 238 if (value instanceof Number) return ((Number) value).shortValue(); 239 return Short.parseShort(value.toString()); 240 } 241 242 @Override getInt(int column)243 public int getInt(int column) { 244 Object value = get(column); 245 if (value == null) return 0; 246 if (value instanceof Number) return ((Number) value).intValue(); 247 return Integer.parseInt(value.toString()); 248 } 249 250 @Override getLong(int column)251 public long getLong(int column) { 252 Object value = get(column); 253 if (value == null) return 0; 254 if (value instanceof Number) return ((Number) value).longValue(); 255 return Long.parseLong(value.toString()); 256 } 257 258 @Override getFloat(int column)259 public float getFloat(int column) { 260 Object value = get(column); 261 if (value == null) return 0.0f; 262 if (value instanceof Number) return ((Number) value).floatValue(); 263 return Float.parseFloat(value.toString()); 264 } 265 266 @Override getDouble(int column)267 public double getDouble(int column) { 268 Object value = get(column); 269 if (value == null) return 0.0d; 270 if (value instanceof Number) return ((Number) value).doubleValue(); 271 return Double.parseDouble(value.toString()); 272 } 273 274 @Override getBlob(int column)275 public byte[] getBlob(int column) { 276 Object value = get(column); 277 return (byte[]) value; 278 } 279 280 @Override getType(int column)281 public int getType(int column) { 282 return DatabaseUtils.getTypeOfObject(get(column)); 283 } 284 285 @Override isNull(int column)286 public boolean isNull(int column) { 287 return get(column) == null; 288 } 289 } 290