001package jmri.jmrit.logixng;
002
003import java.io.File;
004import java.io.FileNotFoundException;
005import javax.annotation.CheckForNull;
006import javax.annotation.CheckReturnValue;
007import javax.annotation.Nonnull;
008
009/**
010 * Represent a Table.
011 * A table is a two dimensional array where the rows and columns may have names.
012 *
013 * @author Daniel Bergqvist Copyright (C) 2019
014 */
015public interface Table {
016
017    /**
018     * Get the value of a cell.
019     * If the table has both rows and columns, the value of the first column
020     * will be returned.
021     * @param row the row of the cell or null if all rows should be returned
022     * @return the value of the cell
023     */
024    @CheckReturnValue
025    default Object getCell(int row) {
026        return getCell(row, 1);
027    }
028
029    /**
030     * Get the value of a cell.
031     * @param row the row of the cell
032     * @param column the column of the cell
033     * @return the value of the cell
034     */
035    @CheckReturnValue
036    Object getCell(int row, int column);
037
038    /**
039     * Get the value of a cell.
040     * If the table has both rows and columns, the value of the first column
041     * will be returned.
042     * @param row the row of the cell or null if all rows should be returned
043     * @return the value of the cell
044     * @throws RowNotFoundException if the row is not found
045     */
046    @CheckReturnValue
047    default Object getCell(@Nonnull String row)
048            throws RowNotFoundException {
049        return getCell(getRowNumber(row), 1);
050    }
051
052    /**
053     * Get the value of a cell.
054     * @param row the row of the cell. If this string is a name of a row, that
055     * row is used. If it's not a name of a row, but an integer value, that
056     * index is used, where row 0 is the name of the row.
057     * @param column the column of the cell. If this string is a name of a
058     * column, that column is used. If it's not a name of a column, but an
059     * integer value, that index is used, where column 0 is the name of the
060     * column.
061     * @return the value of the cell
062     * @throws RowNotFoundException if the row is not found
063     * @throws ColumnNotFoundException if the column is not found
064     */
065    default Object getCell(@Nonnull String row, @Nonnull String column)
066            throws RowNotFoundException, ColumnNotFoundException {
067        return getCell(getRowNumber(row), getColumnNumber(column));
068    }
069
070    /**
071     * Get the value of a cell.
072     * @param value the new value of the cell
073     * @param row the row of the cell
074     * @param column the column of the cell
075     */
076    @CheckReturnValue
077    void setCell(Object value, int row, int column);
078
079    /**
080     * Set the value of a cell.
081     * If the table has both rows and columns, the value of the first column
082     * will be returned.
083     * @param value the new value of the cell
084     * @param row the row of the cell
085     * @throws RowNotFoundException if the row is not found
086     */
087    default void setCell(Object value, String row)
088            throws RowNotFoundException {
089        setCell(value, getRowNumber(row), 1);
090    }
091
092    /**
093     * Set the value of a cell.
094     * @param value the new value of the cell
095     * @param row the row of the cell. If this string is a name of a row, that
096     * row is used. If it's not a name of a row, but an integer value, that
097     * index is used, where row 0 is the name of the row.
098     * @param column the column of the cell. If this string is a name of a
099     * column, that column is used. If it's not a name of a column, but an
100     * integer value, that index is used, where column 0 is the name of the column.
101     * @throws RowNotFoundException if the row is not found
102     * @throws ColumnNotFoundException if the column is not found
103     */
104    default void setCell(Object value, String row, String column)
105            throws RowNotFoundException, ColumnNotFoundException {
106        setCell(value, getRowNumber(row), getColumnNumber(column));
107    }
108
109    /**
110     * Get the number of rows in the table.
111     * @return the number of rows
112     */
113    int numRows();
114
115    /**
116     * Get the number of columns in the table.
117     * @return the number of columns
118     */
119    int numColumns();
120
121    /**
122     * Get the row number by name of row.
123     * @param rowName the name of the row. If there is no row with this name,
124     * and rowName is a positive integer, that row number will be returned.
125     * @return the row number
126     * @throws RowNotFoundException if the row is not found
127     */
128    int getRowNumber(String rowName) throws RowNotFoundException;
129
130    /**
131     * Get the row number by name of row.
132     * @param columnName the name of the column. If there is no column with
133     * this name, and columnName is a positive integer, that column number will
134     * be returned.
135     * @return the column number
136     * @throws ColumnNotFoundException if the column is not found
137     */
138    int getColumnNumber(String columnName) throws ColumnNotFoundException;
139
140    /**
141     * The available types of CSV from which to load a table
142     * The default is TABBED, as that was previously the only choice
143     * TABBED results in parsing the CSV file with tabs as the delimiters
144     * COMMA uses csvFormat of RFC-4180, which is the standard Comma
145     * Seperated Value format, but does not allow empty lines.
146     * SEMICOLON uses a modified version of RFC-4180 with the semicolon as the field delimiter.
147     */
148    enum CsvType {
149
150        TABBED(Bundle.getMessage("CsvType_Tabbed")),
151        COMMA(Bundle.getMessage("CsvType_Comma")),
152        SEMICOLON(Bundle.getMessage("CsvType_Semicolon"));
153
154        private final String _text;
155
156        private CsvType(String text) {
157            this._text = text;
158        }
159
160        @Override
161        public String toString() {
162            return _text;
163        }
164
165    }
166
167    default boolean isCsvTypeSupported() {
168        return false;
169    }
170
171    default void setCsvType(CsvType csvType) {
172        throw new UnsupportedOperationException("Not supported");
173    }
174
175    default CsvType getCsvType() {
176        throw new UnsupportedOperationException("Not supported");
177    }
178
179    /**
180     * Store the table to a CSV file.
181     * @param file the CSV file
182     * @throws java.io.FileNotFoundException if file not found
183     */
184    void storeTableAsCSV(@Nonnull File file)
185            throws FileNotFoundException;
186
187    /**
188     * Store the table to a CSV file.
189     * If system name and/or user name is not null, these values are used
190     * instead of the tables own system name and user name. If no system name
191     * and user name is given and the table is anonymous, no system name and
192     * user name is stored in the file.
193     * @param file the CSV file
194     * @param systemName the system name of the table
195     * @param userName the user name of the table
196     * @throws java.io.FileNotFoundException if file not found
197     */
198    void storeTableAsCSV(
199            @Nonnull File file,
200            @CheckForNull String systemName, @CheckForNull String userName)
201            throws FileNotFoundException;
202
203
204
205
206    class RowNotFoundException extends IllegalArgumentException {
207
208        /**
209         * Constructs a <code>RowNotFoundException</code>.
210         *
211         * @param  name the name of the row.
212         */
213        public RowNotFoundException(String name) {
214            super(Bundle.getMessage("Table_RowNotFound", name));
215        }
216
217        /**
218         * Constructs a <code>RowNotFoundException</code>.
219         *
220         * <p>Note that the detail message associated with <code>cause</code> is
221         * <i>not</i> automatically incorporated in this exception's detail
222         * message.
223         *
224         * @param  name the name of the row.
225         * @param  cause the cause (which is saved for later retrieval by the
226         *         {@link Throwable#getCause()} method).  (A {@code null} value
227         *         is permitted, and indicates that the cause is nonexistent or
228         *         unknown.)
229         */
230        public RowNotFoundException(String name, Throwable cause) {
231            super(Bundle.getMessage("Table_RowNotFound", name), cause);
232        }
233
234    }
235
236
237    class ColumnNotFoundException extends IllegalArgumentException {
238
239        /**
240         * Constructs a <code>ColumnNotFoundException</code>.
241         *
242         * @param  name the name of the row.
243         */
244        public ColumnNotFoundException(String name) {
245            super(Bundle.getMessage("Table_ColumnNotFound", name));
246        }
247
248        /**
249         * Constructs a <code>ColumnNotFoundException</code>.
250         *
251         * <p>Note that the detail message associated with <code>cause</code> is
252         * <i>not</i> automatically incorporated in this exception's detail
253         * message.
254         *
255         * @param  name the name of the row.
256         * @param  cause the cause (which is saved for later retrieval by the
257         *         {@link Throwable#getCause()} method).  (A {@code null} value
258         *         is permitted, and indicates that the cause is nonexistent or
259         *         unknown.)
260         */
261        public ColumnNotFoundException(String name, Throwable cause) {
262            super(Bundle.getMessage("Table_ColumnNotFound", name), cause);
263        }
264
265    }
266
267}