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