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 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}