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
17package android.database;
18
19import 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 */
26public 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