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