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        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     */
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     */
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}. */
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. */
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
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         */
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
218    public int getCount() {
219        return rowCount;
220    }
221
222    @Override
223    public String[] getColumnNames() {
224        return columnNames;
225    }
226
227    @Override
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
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
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
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
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
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
275    public boolean isNull(int column) {
276        return get(column) == null;
277    }
278}
279