001package jmri.jmrit.logixng.implementation; 002 003import java.beans.PropertyChangeEvent; 004import java.io.*; 005import java.util.*; 006 007import javax.annotation.CheckForNull; 008import javax.annotation.Nonnull; 009 010import org.apache.commons.csv.CSVFormat; 011import org.apache.commons.csv.CSVRecord; 012import org.apache.commons.io.FileUtils; 013import org.apache.commons.io.input.BOMInputStream; 014import org.apache.commons.io.input.CharSequenceReader; 015 016import jmri.*; 017import jmri.implementation.AbstractNamedBean; 018import jmri.jmrit.logixng.*; 019import jmri.util.FileUtil; 020 021/** 022 * The default implementation of a NamedTable 023 * 024 * @author Daniel Bergqvist 2018 025 * @author J. Scott Walton (c) 2022 (Csv Types) 026 */ 027public abstract class AbstractNamedTable extends AbstractNamedBean implements NamedTable { 028 029 private int _state = NamedBean.UNKNOWN; 030 protected final AnonymousTable _internalTable; 031 032 /** 033 * Create a new named table. 034 * 035 * @param sys the system name 036 * @param user the user name or null if no user name 037 * @param numRows the number or rows in the table 038 * @param numColumns the number of columns in the table 039 * @throws BadUserNameException when needed 040 * @throws BadSystemNameException when needed 041 */ 042 public AbstractNamedTable(@Nonnull String sys, 043 @CheckForNull String user, 044 int numRows, 045 int numColumns) 046 throws BadUserNameException, BadSystemNameException { 047 super(sys, user); 048 _internalTable = new DefaultAnonymousTable(numRows, numColumns); 049 } 050 051 /** 052 * Create a new named table with an existing array of cells. 053 * Row 0 has the column names and column 0 has the row names. 054 * 055 * @param systemName the system name 056 * @param userName the user name 057 * @param data the data in the table. Note that this data is not 058 * copied to a new array but used by the table as is. 059 * @throws BadUserNameException when needed 060 * @throws BadSystemNameException when needed 061 */ 062 public AbstractNamedTable(@Nonnull String systemName, 063 @CheckForNull String userName, 064 @Nonnull Object[][] data) 065 throws BadUserNameException, BadSystemNameException { 066 super(systemName, userName); 067 068 // Do this test here to ensure all the tests are using correct system names 069 Manager.NameValidity isNameValid = InstanceManager.getDefault(NamedTableManager.class).validSystemNameFormat(mSystemName); 070 if (isNameValid != Manager.NameValidity.VALID) { 071 throw new IllegalArgumentException("system name is not valid"); 072 } 073 _internalTable = new DefaultAnonymousTable(data); 074 } 075 076 /** 077 * Create a new named table with an existing array of cells. 078 * Row 0 has the column names and column 0 has the row names. 079 * 080 * @param systemName the system name 081 * @param userName the user name 082 * @param fileName the file name of the CSV table 083 * @param data the data in the table. Note that this data is not 084 * copied to a new array but used by the table as is. 085 * @throws BadUserNameException when needed 086 * @throws BadSystemNameException when needed 087 */ 088 public AbstractNamedTable(@Nonnull String systemName, 089 @CheckForNull String userName, 090 @Nonnull String fileName, 091 @Nonnull Object[][] data) 092 throws BadUserNameException, BadSystemNameException { 093 super(systemName, userName); 094 095 // Do this test here to ensure all the tests are using correct system names 096 Manager.NameValidity isNameValid = InstanceManager.getDefault(NamedTableManager.class).validSystemNameFormat(mSystemName); 097 if (isNameValid != Manager.NameValidity.VALID) { 098 throw new IllegalArgumentException("system name is not valid"); 099 } 100 _internalTable = new DefaultAnonymousTable(data); 101 } 102 103 @Nonnull 104 private static NamedTable loadFromCSV(@Nonnull String systemName, 105 @CheckForNull String userName, 106 @CheckForNull String fileName, 107 @Nonnull List<List<String>> lines, 108 boolean registerInManager, 109 CsvType csvType) 110 throws NamedBean.BadUserNameException, NamedBean.BadSystemNameException { 111 112 NamedTableManager manager = InstanceManager.getDefault(NamedTableManager.class); 113 114 if (userName != null && userName.isEmpty()) { 115 userName = null; 116 } 117 118 // First row is column names. 119 int numRows = lines.size() - 1; 120 121 // If the last row is empty string, ignore it. 122 if (lines.get(lines.size() - 1).isEmpty()) { 123 numRows--; 124 } 125 126 int numColumns = 0; 127 128 String[][] csvCells = new String[numRows + 1][]; 129 for (int rowCount = 0; rowCount < numRows + 1; rowCount++) { 130 csvCells[rowCount] = lines.get(rowCount).toArray(new String[0]); 131 numColumns = Math.max(numColumns, csvCells[rowCount].length); 132 } 133 134 // Ensure all rows have same number of columns 135 log.debug("about to verify csvCells -- size is {}", numRows); 136 for (int rowCount = 0; rowCount < numRows + 1; rowCount++) { 137 Object[] cells = csvCells[rowCount]; 138 if (cells.length < numColumns) { 139 String[] newCells = new String[numColumns]; 140 System.arraycopy(cells, 0, newCells, 0, cells.length); 141 csvCells[rowCount] = newCells; 142 for (int i = cells.length; i < numColumns; i++) { 143 newCells[i] = ""; 144 } 145 csvCells[rowCount] = newCells; 146 } 147 } 148 149 // fileHasSystemUserName is false since this method is currently unable 150 // to load CSV files that have system and user name in the first line 151 // in the file. 152 NamedTable table = new DefaultCsvNamedTable(systemName, userName, fileName, false, csvCells, csvType); 153 154 if (registerInManager) { 155 manager.register(table); 156 } 157 return table; 158 } 159 160 private static List<List<String>> parseCSV(Reader rdr, CSVFormat format) throws IOException { 161 List<List<String>> returnList = new ArrayList<>(); 162 Iterable<CSVRecord> records = format.parse(rdr); 163 records.forEach(record -> { 164 ArrayList<String> currentList = new ArrayList<>(); 165 Iterator<String> itemList = record.iterator(); 166 itemList.forEachRemaining(item -> { 167 currentList.add(item); 168 }); 169 returnList.add(currentList); 170 }); 171 return returnList; 172 } 173 174 @Nonnull 175 public static NamedTable loadTableFromCSV_Text(@Nonnull String systemName, 176 @CheckForNull String userName, 177 @Nonnull String text, 178 boolean registerInManager, 179 CsvType csvType) 180 throws BadUserNameException, BadSystemNameException, IOException { 181 182 //List<String> lines = Arrays.asList(text.split("\\r?\\n", -1)); 183 Reader rdr = new CharSequenceReader(text); 184 List<List<String>> lines = parseCSV(rdr, CSVFormat.TDF); 185 return loadFromCSV(systemName, userName, null, lines, registerInManager, csvType); 186 } 187 188 @Nonnull 189 public static NamedTable loadTableFromCSV_File(@Nonnull String systemName, 190 @CheckForNull String userName, 191 @Nonnull String fileName, 192 boolean registerInManager, 193 CsvType csvType) 194 throws NamedBean.BadUserNameException, NamedBean.BadSystemNameException, IOException { 195 196 //List<String> lines = Files.readAllLines(FileUtil.getFile(fileName).toPath(), StandardCharsets.UTF_8); 197 List<List<String>> lines = readIt(FileUtil.getFile(fileName), csvType); 198 return loadFromCSV(systemName, userName, fileName, lines, registerInManager, csvType); 199 } 200 201 @Nonnull 202 public static NamedTable loadTableFromCSV_File(@Nonnull String systemName, 203 @CheckForNull String userName, 204 @Nonnull File file, 205 boolean registerInManager, 206 CsvType csvType) 207 throws NamedBean.BadUserNameException, NamedBean.BadSystemNameException, IOException { 208 209 //List<String> lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); 210 List<List<String>> lines = readIt(file, csvType); 211 return loadFromCSV(systemName, userName, file.getPath(), lines, registerInManager, csvType); 212 } 213 214 private static List<List<String>> readIt(File infile, CsvType csvType) throws IOException { 215 List<List<String>> returnList = null; 216 InputStream in = null; 217 in = FileUtils.openInputStream(infile); 218 BOMInputStream bomInputStream = new BOMInputStream(in); 219 if (bomInputStream.hasBOM()) { 220 log.debug("Input file has a Byte Order Marker attached"); 221 } 222 InputStreamReader rdr = new InputStreamReader(bomInputStream); 223 BufferedReader buffered = new BufferedReader(rdr); 224 CSVFormat format = null; 225 if (csvType == CsvType.TABBED) { 226 format = CSVFormat.TDF; 227 } else if (csvType == CsvType.COMMA) { 228 format = CSVFormat.RFC4180; 229 } else if (csvType == CsvType.SEMICOLON) { 230 format = CSVFormat.Builder.create(CSVFormat.RFC4180).setDelimiter(';').build(); 231 } else { 232 buffered.close(); 233 throw new IOException("Unrecognized CSV Format"); 234 } 235 returnList = parseCSV(buffered, format); 236 rdr.close(); 237 return returnList; 238 } 239 240 /** 241 * {@inheritDoc} 242 */ 243 @Override 244 public void storeTableAsCSV(@Nonnull File file) throws FileNotFoundException { 245 _internalTable.storeTableAsCSV(file, getSystemName(), getUserName()); 246 } 247 248 /** 249 * {@inheritDoc} 250 */ 251 @Override 252 public void storeTableAsCSV(@Nonnull File file, boolean storeSystemUserName) 253 throws FileNotFoundException { 254 _internalTable.storeTableAsCSV(file, getSystemName(), getUserName(), storeSystemUserName); 255 } 256 257 /** 258 * {@inheritDoc} 259 */ 260 @Override 261 public void storeTableAsCSV(@Nonnull File file, 262 @CheckForNull String systemName, 263 @CheckForNull String userName) 264 throws FileNotFoundException { 265 266 storeTableAsCSV(file, systemName, userName, true); 267 } 268 269 /** 270 * {@inheritDoc} 271 */ 272 @Override 273 public void storeTableAsCSV( 274 @Nonnull File file, 275 @CheckForNull String systemName, @CheckForNull String userName, 276 boolean storeSystemUserName) 277 throws FileNotFoundException { 278 279 _internalTable.storeTableAsCSV(file, systemName, userName, storeSystemUserName); 280 } 281 282 @Override 283 public void setState(int s) throws JmriException { 284 _state = s; 285 } 286 287 @Override 288 public int getState() { 289 return _state; 290 } 291 292 @Override 293 public String getBeanType() { 294 return Bundle.getMessage("BeanNameTable"); 295 // return Manager.LOGIXNGS; 296 // return NamedTable.class; 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override 303 public Object getCell(int row, int column) { 304 return _internalTable.getCell(row, column); 305 } 306 307 /** 308 * {@inheritDoc} 309 */ 310 @Override 311 public void setCell(Object value, int row, int column) { 312 Object oldValue = _internalTable.getCell(row, column); 313 _internalTable.setCell(value, row, column); 314 PropertyChangeEvent evt = new NamedTablePropertyChangeEvent(this, PROPERTY_CELL_CHANGED, oldValue, value, row, column); 315 firePropertyChange(evt); 316 } 317 318 /** 319 * {@inheritDoc} 320 */ 321 @Override 322 public int numRows() { 323 return _internalTable.numRows(); 324 } 325 326 /** 327 * {@inheritDoc} 328 */ 329 @Override 330 public int numColumns() { 331 return _internalTable.numColumns(); 332 } 333 334 /** 335 * {@inheritDoc} 336 */ 337 @Override 338 public int getRowNumber(String rowName) { 339 return _internalTable.getRowNumber(rowName); 340 } 341 342 /** 343 * {@inheritDoc} 344 */ 345 @Override 346 public int getColumnNumber(String columnName) { 347 return _internalTable.getColumnNumber(columnName); 348 } 349 350 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractNamedTable.class); 351 352 /* 353 protected void insertColumn(int col) { 354 _internalTable.insertColumn(col); 355 } 356 357 protected void deleteColumn(int col) { 358 _internalTable.deleteColumn(col); 359 } 360 361 protected void insertRow(int row) { 362 _internalTable.insertRow(row); 363 } 364 365 protected void deleteRow(int row) { 366 _internalTable.deleteRow(row); 367 } 368 */ 369}