001package jmri.jmrit.logixng.implementation; 002 003import java.io.*; 004import java.util.ArrayList; 005import java.util.Iterator; 006import java.util.List; 007 008import javax.annotation.CheckForNull; 009import javax.annotation.Nonnull; 010 011import jmri.InstanceManager; 012import jmri.JmriException; 013import jmri.Manager; 014import jmri.NamedBean; 015import jmri.implementation.AbstractNamedBean; 016import jmri.jmrit.logixng.AnonymousTable; 017import jmri.jmrit.logixng.NamedTable; 018import jmri.jmrit.logixng.NamedTableManager; 019import jmri.util.FileUtil; 020import org.apache.commons.csv.CSVRecord; 021import org.apache.commons.io.FileUtils; 022import org.apache.commons.io.input.BOMInputStream; 023import org.apache.commons.csv.CSVFormat; 024import org.apache.commons.io.input.CharSequenceReader; 025 026/** 027 * The default implementation of a NamedTable 028 * 029 * @author Daniel Bergqvist 2018 030 * @author J. Scott Walton (c) 2022 (Csv Types) 031 */ 032public abstract class AbstractNamedTable extends AbstractNamedBean implements NamedTable { 033 034 private int _state = NamedBean.UNKNOWN; 035 protected final AnonymousTable _internalTable; 036 037 /** 038 * Create a new named table. 039 * 040 * @param sys the system name 041 * @param user the user name or null if no user name 042 * @param numRows the number or rows in the table 043 * @param numColumns the number of columns in the table 044 */ 045 public AbstractNamedTable(@Nonnull String sys, 046 @CheckForNull String user, 047 int numRows, 048 int numColumns) 049 throws BadUserNameException, BadSystemNameException { 050 super(sys, user); 051 _internalTable = new DefaultAnonymousTable(numRows, numColumns); 052 } 053 054 /** 055 * Create a new named table with an existing array of cells. 056 * Row 0 has the column names and column 0 has the row names. 057 * 058 * @param systemName the system name 059 * @param userName the user name 060 * @param data the data in the table. Note that this data is not 061 * copied to a new array but used by the table as is. 062 */ 063 public AbstractNamedTable(@Nonnull String systemName, 064 @CheckForNull String userName, 065 @Nonnull Object[][] data) 066 throws BadUserNameException, BadSystemNameException { 067 super(systemName, userName); 068 069 // Do this test here to ensure all the tests are using correct system names 070 Manager.NameValidity isNameValid = InstanceManager.getDefault(NamedTableManager.class).validSystemNameFormat(mSystemName); 071 if (isNameValid != Manager.NameValidity.VALID) { 072 throw new IllegalArgumentException("system name is not valid"); 073 } 074 _internalTable = new DefaultAnonymousTable(data); 075 } 076 077 /** 078 * Create a new named table with an existing array of cells. 079 * Row 0 has the column names and column 0 has the row names. 080 * 081 * @param systemName the system name 082 * @param userName the user name 083 * @param fileName the file name of the CSV table 084 * @param data the data in the table. Note that this data is not 085 * copied to a new array but used by the table as is. 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 { 227 buffered.close(); 228 throw new IOException("Unrecognized CSV Format"); 229 } 230 returnList = parseCSV(buffered, format); 231 rdr.close(); 232 return returnList; 233 } 234 235 236 /** 237 * {@inheritDoc} 238 */ 239 @Override 240 public void storeTableAsCSV(@Nonnull File file) throws FileNotFoundException { 241 _internalTable.storeTableAsCSV(file, getSystemName(), getUserName()); 242 } 243 244 /** 245 * {@inheritDoc} 246 */ 247 @Override 248 public void storeTableAsCSV(@Nonnull File file, 249 @CheckForNull String systemName, 250 @CheckForNull String userName) 251 throws FileNotFoundException { 252 253 _internalTable.storeTableAsCSV(file, systemName, userName); 254 } 255 256 @Override 257 public void setState(int s) throws JmriException { 258 _state = s; 259 } 260 261 @Override 262 public int getState() { 263 return _state; 264 } 265 266 @Override 267 public String getBeanType() { 268 return Bundle.getMessage("BeanNameTable"); 269 // return Manager.LOGIXNGS; 270 // return NamedTable.class; 271 } 272 273 /** 274 * {@inheritDoc} 275 */ 276 @Override 277 public Object getCell(int row, int column) { 278 return _internalTable.getCell(row, column); 279 } 280 281 /** 282 * {@inheritDoc} 283 */ 284 @Override 285 public void setCell(Object value, int row, int column) { 286 _internalTable.setCell(value, row, column); 287 } 288 289 /** 290 * {@inheritDoc} 291 */ 292 @Override 293 public int numRows() { 294 return _internalTable.numRows(); 295 } 296 297 /** 298 * {@inheritDoc} 299 */ 300 @Override 301 public int numColumns() { 302 return _internalTable.numColumns(); 303 } 304 305 /** 306 * {@inheritDoc} 307 */ 308 @Override 309 public int getRowNumber(String rowName) { 310 return _internalTable.getRowNumber(rowName); 311 } 312 313 /** 314 * {@inheritDoc} 315 */ 316 @Override 317 public int getColumnNumber(String columnName) { 318 return _internalTable.getColumnNumber(columnName); 319 } 320 321 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractNamedTable.class); 322 323/* 324 protected void insertColumn(int col) { 325 _internalTable.insertColumn(col); 326 } 327 328 protected void deleteColumn(int col) { 329 _internalTable.deleteColumn(col); 330 } 331 332 protected void insertRow(int row) { 333 _internalTable.insertRow(row); 334 } 335 336 protected void deleteRow(int row) { 337 _internalTable.deleteRow(row); 338 } 339*/ 340}