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 using the filename given when loading the
181     * CSV file.
182     * @throws java.io.FileNotFoundException if file not found
183     */
184    void storeTableAsCSV() throws FileNotFoundException;
185
186    /**
187     * Store the table to a CSV file.
188     * @param file the CSV file
189     * @throws java.io.FileNotFoundException if file not found
190     */
191    void storeTableAsCSV(@Nonnull File file)
192            throws FileNotFoundException;
193
194    /**
195     * Store the table to a CSV file.
196     * @param file the CSV file
197     * @param storeSystemUserName true if to store system name and user name in
198     *                            the file, false otherwise
199     * @throws java.io.FileNotFoundException if file not found
200     */
201    void storeTableAsCSV(@Nonnull File file, boolean storeSystemUserName)
202            throws FileNotFoundException;
203
204    /**
205     * Store the table to a CSV file.
206     * If system name and/or user name is not null, these values are used
207     * instead of the tables own system name and user name. If no system name
208     * and user name is given and the table is anonymous, no system name and
209     * user name is stored in the file.
210     * @param file the CSV file
211     * @param systemName the system name of the table
212     * @param userName the user name of the table
213     * @throws java.io.FileNotFoundException if file not found
214     */
215    void storeTableAsCSV(
216            @Nonnull File file,
217            @CheckForNull String systemName, @CheckForNull String userName)
218            throws FileNotFoundException;
219
220    /**
221     * Store the table to a CSV file.
222     * If system name and/or user name is not null, these values are used
223     * instead of the tables own system name and user name. If no system name
224     * and user name is given and the table is anonymous, no system name and
225     * user name is stored in the file.
226     * @param file the CSV file
227     * @param systemName the system name of the table
228     * @param userName the user name of the table
229     * @param storeSystemUserName true if to store system name and user name in
230     *                            the file, false otherwise
231     * @throws java.io.FileNotFoundException if file not found
232     */
233    void storeTableAsCSV(
234            @Nonnull File file,
235            @CheckForNull String systemName, @CheckForNull String userName,
236            boolean storeSystemUserName)
237            throws FileNotFoundException;
238
239
240
241
242    class RowNotFoundException extends IllegalArgumentException {
243
244        /**
245         * Constructs a <code>RowNotFoundException</code>.
246         *
247         * @param  name the name of the row.
248         */
249        public RowNotFoundException(String name) {
250            super(Bundle.getMessage("Table_RowNotFound", name));
251        }
252
253        /**
254         * Constructs a <code>RowNotFoundException</code>.
255         *
256         * <p>Note that the detail message associated with <code>cause</code> is
257         * <i>not</i> automatically incorporated in this exception's detail
258         * message.
259         *
260         * @param  name the name of the row.
261         * @param  cause the cause (which is saved for later retrieval by the
262         *         {@link Throwable#getCause()} method).  (A {@code null} value
263         *         is permitted, and indicates that the cause is nonexistent or
264         *         unknown.)
265         */
266        public RowNotFoundException(String name, Throwable cause) {
267            super(Bundle.getMessage("Table_RowNotFound", name), cause);
268        }
269
270    }
271
272
273    class ColumnNotFoundException extends IllegalArgumentException {
274
275        /**
276         * Constructs a <code>ColumnNotFoundException</code>.
277         *
278         * @param  name the name of the row.
279         */
280        public ColumnNotFoundException(String name) {
281            super(Bundle.getMessage("Table_ColumnNotFound", name));
282        }
283
284        /**
285         * Constructs a <code>ColumnNotFoundException</code>.
286         *
287         * <p>Note that the detail message associated with <code>cause</code> is
288         * <i>not</i> automatically incorporated in this exception's detail
289         * message.
290         *
291         * @param  name the name of the row.
292         * @param  cause the cause (which is saved for later retrieval by the
293         *         {@link Throwable#getCause()} method).  (A {@code null} value
294         *         is permitted, and indicates that the cause is nonexistent or
295         *         unknown.)
296         */
297        public ColumnNotFoundException(String name, Throwable cause) {
298            super(Bundle.getMessage("Table_ColumnNotFound", name), cause);
299        }
300
301    }
302
303}