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.text;
18
19import com.android.internal.util.ArrayUtils;
20import com.android.internal.util.GrowingArrayUtils;
21
22
23/**
24 * PackedIntVector stores a two-dimensional array of integers,
25 * optimized for inserting and deleting rows and for
26 * offsetting the values in segments of a given column.
27 */
28class PackedIntVector {
29    private final int mColumns;
30    private int mRows;
31
32    private int mRowGapStart;
33    private int mRowGapLength;
34
35    private int[] mValues;
36    private int[] mValueGap; // starts, followed by lengths
37
38    /**
39     * Creates a new PackedIntVector with the specified width and
40     * a height of 0.
41     *
42     * @param columns the width of the PackedIntVector.
43     */
44    public PackedIntVector(int columns) {
45        mColumns = columns;
46        mRows = 0;
47
48        mRowGapStart = 0;
49        mRowGapLength = mRows;
50
51        mValues = null;
52        mValueGap = new int[2 * columns];
53    }
54
55    /**
56     * Returns the value at the specified row and column.
57     *
58     * @param row the index of the row to return.
59     * @param column the index of the column to return.
60     *
61     * @return the value stored at the specified position.
62     *
63     * @throws IndexOutOfBoundsException if the row is out of range
64     *         (row < 0 || row >= size()) or the column is out of range
65     *         (column < 0 || column >= width()).
66     */
67    public int getValue(int row, int column) {
68        final int columns = mColumns;
69
70        if (((row | column) < 0) || (row >= size()) || (column >= columns)) {
71            throw new IndexOutOfBoundsException(row + ", " + column);
72        }
73
74        if (row >= mRowGapStart) {
75            row += mRowGapLength;
76        }
77
78        int value = mValues[row * columns + column];
79
80        int[] valuegap = mValueGap;
81        if (row >= valuegap[column]) {
82            value += valuegap[column + columns];
83        }
84
85        return value;
86    }
87
88    /**
89     * Sets the value at the specified row and column.
90     *
91     * @param row the index of the row to set.
92     * @param column the index of the column to set.
93     *
94     * @throws IndexOutOfBoundsException if the row is out of range
95     *         (row &lt; 0 || row >= size()) or the column is out of range
96     *         (column &lt; 0 || column >= width()).
97     */
98    public void setValue(int row, int column, int value) {
99        if (((row | column) < 0) || (row >= size()) || (column >= mColumns)) {
100            throw new IndexOutOfBoundsException(row + ", " + column);
101        }
102
103        if (row >= mRowGapStart) {
104            row += mRowGapLength;
105        }
106
107        int[] valuegap = mValueGap;
108        if (row >= valuegap[column]) {
109            value -= valuegap[column + mColumns];
110        }
111
112        mValues[row * mColumns + column] = value;
113    }
114
115    /**
116     * Sets the value at the specified row and column.
117     * Private internal version: does not check args.
118     *
119     * @param row the index of the row to set.
120     * @param column the index of the column to set.
121     *
122     */
123    private void setValueInternal(int row, int column, int value) {
124        if (row >= mRowGapStart) {
125            row += mRowGapLength;
126        }
127
128        int[] valuegap = mValueGap;
129        if (row >= valuegap[column]) {
130            value -= valuegap[column + mColumns];
131        }
132
133        mValues[row * mColumns + column] = value;
134    }
135
136
137    /**
138     * Increments all values in the specified column whose row >= the
139     * specified row by the specified delta.
140     *
141     * @param startRow the row at which to begin incrementing.
142     *        This may be == size(), which case there is no effect.
143     * @param column the index of the column to set.
144     *
145     * @throws IndexOutOfBoundsException if the row is out of range
146     *         (startRow &lt; 0 || startRow > size()) or the column
147     *         is out of range (column &lt; 0 || column >= width()).
148     */
149    public void adjustValuesBelow(int startRow, int column, int delta) {
150        if (((startRow | column) < 0) || (startRow > size()) ||
151                (column >= width())) {
152            throw new IndexOutOfBoundsException(startRow + ", " + column);
153        }
154
155        if (startRow >= mRowGapStart) {
156            startRow += mRowGapLength;
157        }
158
159        moveValueGapTo(column, startRow);
160        mValueGap[column + mColumns] += delta;
161    }
162
163    /**
164     * Inserts a new row of values at the specified row offset.
165     *
166     * @param row the row above which to insert the new row.
167     *        This may be == size(), which case the new row is added
168     *        at the end.
169     * @param values the new values to be added.  If this is null,
170     *        a row of zeroes is added.
171     *
172     * @throws IndexOutOfBoundsException if the row is out of range
173     *         (row &lt; 0 || row > size()) or if the length of the
174     *         values array is too small (values.length < width()).
175     */
176    public void insertAt(int row, int[] values) {
177        if ((row < 0) || (row > size())) {
178            throw new IndexOutOfBoundsException("row " + row);
179        }
180
181        if ((values != null) && (values.length < width())) {
182            throw new IndexOutOfBoundsException("value count " + values.length);
183        }
184
185        moveRowGapTo(row);
186
187        if (mRowGapLength == 0) {
188            growBuffer();
189        }
190
191        mRowGapStart++;
192        mRowGapLength--;
193
194        if (values == null) {
195            for (int i = mColumns - 1; i >= 0; i--) {
196                setValueInternal(row, i, 0);
197            }
198        } else {
199            for (int i = mColumns - 1; i >= 0; i--) {
200                setValueInternal(row, i, values[i]);
201            }
202        }
203    }
204
205    /**
206     * Deletes the specified number of rows starting with the specified
207     * row.
208     *
209     * @param row the index of the first row to be deleted.
210     * @param count the number of rows to delete.
211     *
212     * @throws IndexOutOfBoundsException if any of the rows to be deleted
213     *         are out of range (row &lt; 0 || count &lt; 0 ||
214     *         row + count > size()).
215     */
216    public void deleteAt(int row, int count) {
217        if (((row | count) < 0) || (row + count > size())) {
218            throw new IndexOutOfBoundsException(row + ", " + count);
219        }
220
221        moveRowGapTo(row + count);
222
223        mRowGapStart -= count;
224        mRowGapLength += count;
225
226        // TODO: Reclaim memory when the new height is much smaller
227        // than the allocated size.
228    }
229
230    /**
231     * Returns the number of rows in the PackedIntVector.  This number
232     * will change as rows are inserted and deleted.
233     *
234     * @return the number of rows.
235     */
236    public int size() {
237        return mRows - mRowGapLength;
238    }
239
240    /**
241     * Returns the width of the PackedIntVector.  This number is set
242     * at construction and will not change.
243     *
244     * @return the number of columns.
245     */
246    public int width() {
247        return mColumns;
248    }
249
250    /**
251     * Grows the value and gap arrays to be large enough to store at least
252     * one more than the current number of rows.
253     */
254    private final void growBuffer() {
255        final int columns = mColumns;
256        int[] newvalues = ArrayUtils.newUnpaddedIntArray(
257                GrowingArrayUtils.growSize(size()) * columns);
258        int newsize = newvalues.length / columns;
259
260        final int[] valuegap = mValueGap;
261        final int rowgapstart = mRowGapStart;
262
263        int after = mRows - (rowgapstart + mRowGapLength);
264
265        if (mValues != null) {
266            System.arraycopy(mValues, 0, newvalues, 0, columns * rowgapstart);
267            System.arraycopy(mValues, (mRows - after) * columns,
268                             newvalues, (newsize - after) * columns,
269                             after * columns);
270        }
271
272        for (int i = 0; i < columns; i++) {
273            if (valuegap[i] >= rowgapstart) {
274                valuegap[i] += newsize - mRows;
275
276                if (valuegap[i] < rowgapstart) {
277                    valuegap[i] = rowgapstart;
278                }
279            }
280        }
281
282        mRowGapLength += newsize - mRows;
283        mRows = newsize;
284        mValues = newvalues;
285    }
286
287    /**
288     * Moves the gap in the values of the specified column to begin at
289     * the specified row.
290     */
291    private final void moveValueGapTo(int column, int where) {
292        final int[] valuegap = mValueGap;
293        final int[] values = mValues;
294        final int columns = mColumns;
295
296        if (where == valuegap[column]) {
297            return;
298        } else if (where > valuegap[column]) {
299            for (int i = valuegap[column]; i < where; i++) {
300                values[i * columns + column] += valuegap[column + columns];
301            }
302        } else /* where < valuegap[column] */ {
303            for (int i = where; i < valuegap[column]; i++) {
304                values[i * columns + column] -= valuegap[column + columns];
305            }
306        }
307
308        valuegap[column] = where;
309    }
310
311    /**
312     * Moves the gap in the row indices to begin at the specified row.
313     */
314    private final void moveRowGapTo(int where) {
315        if (where == mRowGapStart) {
316            return;
317        } else if (where > mRowGapStart) {
318            int moving = where + mRowGapLength - (mRowGapStart + mRowGapLength);
319            final int columns = mColumns;
320            final int[] valuegap = mValueGap;
321            final int[] values = mValues;
322            final int gapend = mRowGapStart + mRowGapLength;
323
324            for (int i = gapend; i < gapend + moving; i++) {
325                int destrow = i - gapend + mRowGapStart;
326
327                for (int j = 0; j < columns; j++) {
328                    int val = values[i * columns+ j];
329
330                    if (i >= valuegap[j]) {
331                        val += valuegap[j + columns];
332                    }
333
334                    if (destrow >= valuegap[j]) {
335                        val -= valuegap[j + columns];
336                    }
337
338                    values[destrow * columns + j] = val;
339                }
340            }
341        } else /* where < mRowGapStart */ {
342            int moving = mRowGapStart - where;
343            final int columns = mColumns;
344            final int[] valuegap = mValueGap;
345            final int[] values = mValues;
346            final int gapend = mRowGapStart + mRowGapLength;
347
348            for (int i = where + moving - 1; i >= where; i--) {
349                int destrow = i - where + gapend - moving;
350
351                for (int j = 0; j < columns; j++) {
352                    int val = values[i * columns+ j];
353
354                    if (i >= valuegap[j]) {
355                        val += valuegap[j + columns];
356                    }
357
358                    if (destrow >= valuegap[j]) {
359                        val -= valuegap[j + columns];
360                    }
361
362                    values[destrow * columns + j] = val;
363                }
364            }
365        }
366
367        mRowGapStart = where;
368    }
369}
370