1/*
2 * Copyright (C) 2014 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 com.android.inputmethod.keyboard.layout.expected;
18
19import java.util.Arrays;
20
21/**
22 * This class builds a keyboard that is a two dimensional array of elements <code>E</code>.
23 *
24 * A keyboard consists of an array of rows, and a row consists of an array of elements. Each row
25 * may have different number of elements. A element of a keyboard can be specified by a row number
26 * and a column number, both numbers starts from 1.
27 *
28 * @param <E> the type of a keyboard element. A keyboard element must be an immutable object.
29 */
30abstract class AbstractKeyboardBuilder<E> {
31    // A building array of rows.
32    private E[][] mRows;
33
34    // Returns an instance of default element.
35    abstract E defaultElement();
36    // Returns an <code>E</code> array instance of the <code>size</code>.
37    abstract E[] newArray(final int size);
38    // Returns an <code>E[]</code> array instance of the <code>size</code>.
39    abstract E[][] newArrayOfArray(final int size);
40
41    /**
42     * Construct an empty builder.
43     */
44    AbstractKeyboardBuilder() {
45        mRows = newArrayOfArray(0);
46    }
47
48    /**
49     * Construct a builder from template keyboard. This builder has the same dimensions and
50     * elements of <code>rows</rows>.
51     * @param rows the template keyboard rows. The elements of the <code>rows</code> will be
52     *        shared with this builder. Therefore a element must be an immutable object.
53     */
54    AbstractKeyboardBuilder(final E[][] rows) {
55        mRows = newArrayOfArray(rows.length);
56        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
57            final E[] row = rows[rowIndex];
58            mRows[rowIndex] = Arrays.copyOf(row, row.length);
59        }
60    }
61
62    /**
63     * Return current constructing keyboard.
64     * @return the array of the array of the element being constructed.
65     */
66    E[][] build() {
67        return mRows;
68    }
69
70    /**
71     * Return the number of rows.
72     * @return the number of rows being constructed.
73     */
74    int getRowCount() {
75        return mRows.length;
76    }
77
78    /**
79     * Get the current contents of the specified row.
80     * @param row the row number to get the contents.
81     * @return the array of elements at row number <code>row</code>.
82     * @throws RuntimeException if <code>row</code> is illegal.
83     */
84    E[] getRowAt(final int row) {
85        final int rowIndex = row - 1;
86        if (rowIndex < 0 || rowIndex >= mRows.length) {
87            throw new RuntimeException("Illegal row number: " + row);
88        }
89        return mRows[rowIndex];
90    }
91
92    /**
93     * Set an array of elements to the specified row.
94     * @param row the row number to set <code>elements</code>.
95     * @param elements the array of elements to set at row number <code>row</code>.
96     * @throws RuntimeException if <code>row</code> is illegal.
97     */
98    void setRowAt(final int row, final E[] elements) {
99        final int rowIndex = row - 1;
100        if (rowIndex < 0) {
101            throw new RuntimeException("Illegal row number: " + row);
102        }
103        final E[][] newRows = (rowIndex < mRows.length) ? mRows
104                : Arrays.copyOf(mRows, rowIndex + 1);
105        newRows[rowIndex] = elements;
106        mRows = newRows;
107    }
108
109    /**
110     * Set or insert an element at specified position.
111     * @param row the row number to set or insert the <code>element</code>.
112     * @param column the column number to set or insert the <code>element</code>.
113     * @param element the element to set or insert at <code>row,column</code>.
114     * @param insert if true, the <code>element</code> is inserted at <code>row,column</code>.
115     *        Otherwise the <code>element</code> replace the element at <code>row,column</code>.
116     * @throws RuntimeException if <code>row</code> or <code>column</code> is illegal.
117     */
118    void setElementAt(final int row, final int column, final E element, final boolean insert) {
119        final E[] elements = getRowAt(row);
120        final int columnIndex = column - 1;
121        if (columnIndex < 0) {
122            throw new RuntimeException("Illegal column number: " + column);
123        }
124        if (insert) {
125            if (columnIndex >= elements.length + 1) {
126                throw new RuntimeException("Illegal column number: " + column);
127            }
128            final E[] newElements = Arrays.copyOf(elements, elements.length + 1);
129            // Shift the remaining elements.
130            System.arraycopy(newElements, columnIndex, newElements, columnIndex + 1,
131                    elements.length - columnIndex);
132            // Insert the element at <code>row,column</code>.
133            newElements[columnIndex] = element;
134            // Replace the current row with one.
135            setRowAt(row, newElements);
136            return;
137        }
138        final E[] newElements  = (columnIndex < elements.length) ? elements
139                : Arrays.copyOf(elements, columnIndex + 1);
140        newElements[columnIndex] = element;
141        setRowAt(row, newElements);
142    }
143}
144