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