001package jmri.jmrit.logixng.actions; 002 003import java.util.*; 004 005import javax.annotation.Nonnull; 006 007import jmri.*; 008import jmri.jmrit.logixng.*; 009import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean; 010import jmri.jmrit.logixng.util.ReferenceUtil; 011import jmri.jmrit.logixng.util.parser.*; 012import jmri.util.TypeConversionUtil; 013 014/** 015 * Executes an action when the expression is True. 016 * 017 * @author Daniel Bergqvist Copyright 2018 018 */ 019public class TableForEach extends AbstractDigitalAction 020 implements FemaleSocketListener { 021 022 private final LogixNG_SelectNamedBean<NamedTable> _selectNamedBean = 023 new LogixNG_SelectNamedBean<>( 024 this, NamedTable.class, InstanceManager.getDefault(NamedTableManager.class)); 025 private NamedBeanAddressing _rowOrColumnAddressing = NamedBeanAddressing.Direct; 026 private TableRowOrColumn _tableRowOrColumn = TableRowOrColumn.Row; 027 private String _rowOrColumnName = ""; 028 private String _rowOrColumnReference = ""; 029 private String _rowOrColumnLocalVariable = ""; 030 private String _rowOrColumnFormula = ""; 031 private ExpressionNode _rowOrColumnExpressionNode; 032 private String _variableName = ""; 033 private String _socketSystemName; 034 private final FemaleDigitalActionSocket _socket; 035 036 public TableForEach(String sys, String user) { 037 super(sys, user); 038 _socket = InstanceManager.getDefault(DigitalActionManager.class) 039 .createFemaleSocket(this, this, "A1"); 040 } 041 042 @Override 043 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 044 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 045 String sysName = systemNames.get(getSystemName()); 046 String userName = userNames.get(getSystemName()); 047 if (sysName == null) sysName = manager.getAutoSystemName(); 048 TableForEach copy = new TableForEach(sysName, userName); 049 copy.setComment(getComment()); 050 _selectNamedBean.copy(copy._selectNamedBean); 051 copy.setRowOrColumnAddressing(_rowOrColumnAddressing); 052 copy.setRowOrColumn(_tableRowOrColumn); 053 copy.setRowOrColumnName(_rowOrColumnName); 054 copy.setLocalVariableName(_variableName); 055 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 056 } 057 058 public LogixNG_SelectNamedBean<NamedTable> getSelectNamedBean() { 059 return _selectNamedBean; 060 } 061 062 /** {@inheritDoc} */ 063 @Override 064 public LogixNG_Category getCategory() { 065 return LogixNG_Category.FLOW_CONTROL; 066 } 067 068 private String getNewRowOrColumnName() throws JmriException { 069 070 switch (_rowOrColumnAddressing) { 071 case Direct: 072 return _rowOrColumnName; 073 074 case Reference: 075 return ReferenceUtil.getReference( 076 getConditionalNG().getSymbolTable(), _rowOrColumnReference); 077 078 case LocalVariable: 079 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 080 return TypeConversionUtil 081 .convertToString(symbolTable.getValue(_rowOrColumnLocalVariable), false); 082 083 case Formula: 084 return _rowOrColumnExpressionNode != null 085 ? TypeConversionUtil.convertToString( 086 _rowOrColumnExpressionNode.calculate( 087 getConditionalNG().getSymbolTable()), false) 088 : null; 089 090 default: 091 throw new IllegalArgumentException("invalid _rowOrColumnAddressing state: " + _rowOrColumnAddressing.name()); 092 } 093 } 094 095 /** {@inheritDoc} */ 096 @Override 097 public void execute() throws JmriException { 098 Table table = _selectNamedBean.evaluateNamedBean(getConditionalNG()); 099 100 if (table == null) { 101 return; 102 } 103 104 String rowOrColumnName = getNewRowOrColumnName(); 105 106 if (rowOrColumnName == null) { 107 log.error("rowOrColumnName is null"); 108 return; 109 } 110 if (_variableName == null) { 111 log.error("variableName is null"); 112 return; 113 } 114 if (!_socket.isConnected()) { 115 log.error("socket is not connected"); 116 return; 117 } 118 119 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 120 121 if (_tableRowOrColumn == TableRowOrColumn.Row) { 122 int row = 0; // Empty row name is header row 123 if (!rowOrColumnName.isEmpty()) { 124 row = table.getRowNumber(rowOrColumnName); 125 } 126 for (int column=1; column <= table.numColumns(); column++) { 127 // If the header is null or empty, treat the row as a comment 128 Object header = table.getCell(0, column); 129 if ((header != null) && (!header.toString().isEmpty())) { 130 symbolTable.setValue(_variableName, table.getCell(row, column)); 131 try { 132 _socket.execute(); 133 } catch (BreakException e) { 134 break; 135 } catch (ContinueException e) { 136 // Do nothing, just catch it. 137 } 138 } 139 } 140 } else { 141 int column = 0; // Empty column name is header column 142 if (!rowOrColumnName.isEmpty()) { 143 column = table.getColumnNumber(rowOrColumnName); 144 } 145 for (int row=1; row <= table.numRows(); row++) { 146 // If the header is null or empty, treat the row as a comment 147 Object header = table.getCell(row, 0); 148// System.out.format("Column header: %s%n", header); 149 if ((header != null) && (!header.toString().isEmpty())) { 150 symbolTable.setValue(_variableName, table.getCell(row, column)); 151// System.out.format("Variable: %s, value: %s%n", _variableName, table.getCell(row, column)); 152 try { 153 _socket.execute(); 154 } catch (BreakException e) { 155 break; 156 } catch (ContinueException e) { 157 // Do nothing, just catch it. 158 } 159 } 160 } 161 } 162 } 163 164 /** 165 * Get tableRowOrColumn. 166 * @return tableRowOrColumn 167 */ 168 public TableRowOrColumn getRowOrColumn() { 169 return _tableRowOrColumn; 170 } 171 172 /** 173 * Set tableRowOrColumn. 174 * @param tableRowOrColumn tableRowOrColumn 175 */ 176 public void setRowOrColumn(@Nonnull TableRowOrColumn tableRowOrColumn) { 177 _tableRowOrColumn = tableRowOrColumn; 178 } 179 180 public void setRowOrColumnAddressing(NamedBeanAddressing addressing) throws ParserException { 181 _rowOrColumnAddressing = addressing; 182 parseRowOrColumnFormula(); 183 } 184 185 public NamedBeanAddressing getRowOrColumnAddressing() { 186 return _rowOrColumnAddressing; 187 } 188 189 /** 190 * Get name of row or column 191 * @return name of row or column 192 */ 193 public String getRowOrColumnName() { 194 return _rowOrColumnName; 195 } 196 197 /** 198 * Set name of row or column 199 * @param rowOrColumnName name of row or column 200 */ 201 public void setRowOrColumnName(@Nonnull String rowOrColumnName) { 202 if (rowOrColumnName == null) throw new RuntimeException("Daniel"); 203 _rowOrColumnName = rowOrColumnName; 204 } 205 206 public void setRowOrColumnReference(@Nonnull String reference) { 207 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 208 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 209 } 210 _rowOrColumnReference = reference; 211 } 212 213 public String getRowOrColumnReference() { 214 return _rowOrColumnReference; 215 } 216 217 public void setRowOrColumnLocalVariable(@Nonnull String localVariable) { 218 _rowOrColumnLocalVariable = localVariable; 219 } 220 221 public String getRowOrColumnLocalVariable() { 222 return _rowOrColumnLocalVariable; 223 } 224 225 public void setRowOrColumnFormula(@Nonnull String formula) throws ParserException { 226 _rowOrColumnFormula = formula; 227 parseRowOrColumnFormula(); 228 } 229 230 public String getRowOrColumnFormula() { 231 return _rowOrColumnFormula; 232 } 233 234 private void parseRowOrColumnFormula() throws ParserException { 235 if (_rowOrColumnAddressing == NamedBeanAddressing.Formula) { 236 Map<String, Variable> variables = new HashMap<>(); 237 238 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 239 _rowOrColumnExpressionNode = parser.parseExpression(_rowOrColumnFormula); 240 } else { 241 _rowOrColumnExpressionNode = null; 242 } 243 } 244 245 /** 246 * Get name of local variable 247 * @return name of local variable 248 */ 249 public String getLocalVariableName() { 250 return _variableName; 251 } 252 253 /** 254 * Set name of local variable 255 * @param localVariableName name of local variable 256 */ 257 public void setLocalVariableName(String localVariableName) { 258 _variableName = localVariableName; 259 } 260 261 @Override 262 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 263 switch (index) { 264 case 0: 265 return _socket; 266 267 default: 268 throw new IllegalArgumentException( 269 String.format("index has invalid value: %d", index)); 270 } 271 } 272 273 @Override 274 public int getChildCount() { 275 return 1; 276 } 277 278 @Override 279 public void connected(FemaleSocket socket) { 280 if (socket == _socket) { 281 _socketSystemName = socket.getConnectedSocket().getSystemName(); 282 } else { 283 throw new IllegalArgumentException("unkown socket"); 284 } 285 } 286 287 @Override 288 public void disconnected(FemaleSocket socket) { 289 if (socket == _socket) { 290 _socketSystemName = null; 291 } else { 292 throw new IllegalArgumentException("unkown socket"); 293 } 294 } 295 296 @Override 297 public String getShortDescription(Locale locale) { 298 return Bundle.getMessage(locale, "TableForEach_Short"); 299 } 300 301 @Override 302 public String getLongDescription(Locale locale) { 303 String namedBean = _selectNamedBean.getDescription(locale); 304 String rowOrColumnName; 305 306 switch (_rowOrColumnAddressing) { 307 case Direct: 308 String name = _rowOrColumnName; 309 if (name.isEmpty()) name = Bundle.getMessage("TableForEach_Header"); 310 rowOrColumnName = Bundle.getMessage(locale, "AddressByDirect", name); 311 break; 312 313 case Reference: 314 rowOrColumnName = Bundle.getMessage(locale, "AddressByReference", _rowOrColumnReference); 315 break; 316 317 case LocalVariable: 318 rowOrColumnName = Bundle.getMessage(locale, "AddressByLocalVariable", _rowOrColumnLocalVariable); 319 break; 320 321 case Formula: 322 rowOrColumnName = Bundle.getMessage(locale, "AddressByFormula", _rowOrColumnFormula); 323 break; 324 325 default: 326 throw new IllegalArgumentException("invalid _rowOrColumnAddressing state: " + _rowOrColumnAddressing.name()); 327 } 328 329 return Bundle.getMessage(locale, "TableForEach_Long", 330 _tableRowOrColumn.getOpposite().toStringLowerCase(), 331 _tableRowOrColumn.toStringLowerCase(), 332 rowOrColumnName, 333 namedBean, 334 _variableName, 335 _socket.getName()); 336 } 337 338 public FemaleDigitalActionSocket getSocket() { 339 return _socket; 340 } 341 342 public String getSocketSystemName() { 343 return _socketSystemName; 344 } 345 346 public void setSocketSystemName(String systemName) { 347 _socketSystemName = systemName; 348 } 349 350 /** {@inheritDoc} */ 351 @Override 352 public void setup() { 353 try { 354 if ( !_socket.isConnected() 355 || !_socket.getConnectedSocket().getSystemName() 356 .equals(_socketSystemName)) { 357 358 String socketSystemName = _socketSystemName; 359 _socket.disconnect(); 360 if (socketSystemName != null) { 361 MaleSocket maleSocket = 362 InstanceManager.getDefault(DigitalActionManager.class) 363 .getBySystemName(socketSystemName); 364 _socket.disconnect(); 365 if (maleSocket != null) { 366 _socket.connect(maleSocket); 367 maleSocket.setup(); 368 } else { 369 log.error("cannot load digital action {}", socketSystemName); 370 } 371 } 372 } else { 373 _socket.getConnectedSocket().setup(); 374 } 375 } catch (SocketAlreadyConnectedException ex) { 376 // This shouldn't happen and is a runtime error if it does. 377 throw new RuntimeException("socket is already connected"); 378 } 379 } 380 381 /** {@inheritDoc} */ 382 @Override 383 public void disposeMe() { 384 } 385 386 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TableForEach.class); 387 388}