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 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