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     */
147    enum CsvType {
148
149        TABBED(Bundle.getMessage("CsvType_Tabbed")),
150        COMMA(Bundle.getMessage("CsvType_Comma"));
151
152        private final String _text;
153
154        private CsvType(String text) {
155            this._text = text;
156        }
157
158        @Override
159        public String toString() {
160            return _text;
161        }
162
163    }
164
165    default boolean isCsvTypeSupported() {
166        return false;
167    }
168
169    default void setCsvType(CsvType csvType) {
170        throw new UnsupportedOperationException("Not supported");
171    }
172
173    default CsvType getCsvType() {
174        throw new UnsupportedOperationException("Not supported");
175    }
176
177    /**
178     * Store the table to a CSV file.
179     * @param file the CSV file
180     * @throws java.io.FileNotFoundException if file not found
181     */
182    void storeTableAsCSV(@Nonnull File file)
183            throws FileNotFoundException;
184
185    /**
186     * Store the table to a CSV file.
187     * If system name and/or user name is not null, these values are used
188     * instead of the tables own system name and user name. If no system name
189     * and user name is given and the table is anonymous, no system name and
190     * user name is stored in the file.
191     * @param file the CSV file
192     * @param systemName the system name of the table
193     * @param userName the user name of the table
194     * @throws java.io.FileNotFoundException if file not found
195     */
196    void storeTableAsCSV(
197            @Nonnull File file,
198            @CheckForNull String systemName, @CheckForNull String userName)
199            throws FileNotFoundException;
200
201
202
203
204    class RowNotFoundException extends IllegalArgumentException {
205
206        /**
207         * Constructs a <code>RowNotFoundException</code>.
208         *
209         * @param  name the name of the row.
210         */
211        public RowNotFoundException(String name) {
212            super(Bundle.getMessage("Table_RowNotFound", name));
213        }
214
215        /**
216         * Constructs a <code>RowNotFoundException</code>.
217         *
218         * <p>Note that the detail message associated with <code>cause</code> is
219         * <i>not</i> automatically incorporated in this exception's detail
220         * message.
221         *
222         * @param  name the name of the row.
223         * @param  cause the cause (which is saved for later retrieval by the
224         *         {@link Throwable#getCause()} method).  (A {@code null} value
225         *         is permitted, and indicates that the cause is nonexistent or
226         *         unknown.)
227         */
228        public RowNotFoundException(String name, Throwable cause) {
229            super(Bundle.getMessage("Table_RowNotFound", name), cause);
230        }
231
232    }
233
234
235    class ColumnNotFoundException extends IllegalArgumentException {
236
237        /**
238         * Constructs a <code>ColumnNotFoundException</code>.
239         *
240         * @param  name the name of the row.
241         */
242        public ColumnNotFoundException(String name) {
243            super(Bundle.getMessage("Table_ColumnNotFound", name));
244        }
245
246        /**
247         * Constructs a <code>ColumnNotFoundException</code>.
248         *
249         * <p>Note that the detail message associated with <code>cause</code> is
250         * <i>not</i> automatically incorporated in this exception's detail
251         * message.
252         *
253         * @param  name the name of the row.
254         * @param  cause the cause (which is saved for later retrieval by the
255         *         {@link Throwable#getCause()} method).  (A {@code null} value
256         *         is permitted, and indicates that the cause is nonexistent or
257         *         unknown.)
258         */
259        public ColumnNotFoundException(String name, Throwable cause) {
260            super(Bundle.getMessage("Table_ColumnNotFound", name), cause);
261        }
262
263    }
264
265}