001package jmri.jmrit.logixng.actions; 002 003import java.beans.*; 004import java.util.*; 005import java.util.concurrent.atomic.AtomicReference; 006 007import javax.annotation.Nonnull; 008 009import jmri.*; 010import jmri.jmrit.logixng.*; 011import jmri.jmrit.logixng.util.parser.*; 012import jmri.jmrit.logixng.util.parser.ExpressionNode; 013import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean; 014import jmri.jmrit.logixng.util.LogixNG_SelectTable; 015import jmri.jmrit.logixng.util.ReferenceUtil; 016import jmri.util.ThreadingUtil; 017import jmri.util.TypeConversionUtil; 018 019/** 020 * This action sets a cell value of a LogixNG table. 021 * 022 * @author Daniel Bergqvist Copyright 2024 023 */ 024public class ActionTable extends AbstractDigitalAction 025 implements PropertyChangeListener { 026 027 private final LogixNG_SelectTable _selectTableToSet = 028 new LogixNG_SelectTable(this, () -> {return true; }); 029 030 private final LogixNG_SelectNamedBean<Memory> _selectMemoryNamedBean = 031 new LogixNG_SelectNamedBean<>( 032 this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this); 033 034 private final LogixNG_SelectNamedBean<Block> _selectBlockNamedBean = 035 new LogixNG_SelectNamedBean<>( 036 this, Block.class, InstanceManager.getDefault(BlockManager.class), this); 037 038 private final LogixNG_SelectNamedBean<Reporter> _selectReporterNamedBean = 039 new LogixNG_SelectNamedBean<>( 040 this, Reporter.class, InstanceManager.getDefault(ReporterManager.class), this); 041 042 private VariableOperation _variableOperation = VariableOperation.SetToString; 043 private ConstantType _constantType = ConstantType.String; 044 private String _constantValue = ""; 045 private String _otherLocalVariable = ""; 046 private String _reference = ""; 047 private String _formula = ""; 048 private ExpressionNode _expressionNode; 049 private boolean _listenToMemory = false; 050 private boolean _listenToBlock = false; 051 private boolean _listenToReporter = false; 052 053 private final LogixNG_SelectTable _selectTable = 054 new LogixNG_SelectTable(this, () -> {return _variableOperation == VariableOperation.CopyTableCellToVariable;}); 055 056 057 public ActionTable(String sys, String user) 058 throws BadUserNameException, BadSystemNameException { 059 super(sys, user); 060 061 _selectMemoryNamedBean.setOnlyDirectAddressingAllowed(); 062 _selectBlockNamedBean.setOnlyDirectAddressingAllowed(); 063 _selectReporterNamedBean.setOnlyDirectAddressingAllowed(); 064 } 065 066 @Override 067 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException { 068 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 069 String sysName = systemNames.get(getSystemName()); 070 String userName = userNames.get(getSystemName()); 071 if (sysName == null) sysName = manager.getAutoSystemName(); 072 ActionTable copy = new ActionTable(sysName, userName); 073 copy.setComment(getComment()); 074 _selectTableToSet.copy(copy._selectTableToSet); 075 copy.setVariableOperation(_variableOperation); 076 copy.setConstantType(_constantType); 077 copy.setConstantValue(_constantValue); 078 _selectMemoryNamedBean.copy(copy._selectMemoryNamedBean); 079 _selectBlockNamedBean.copy(copy._selectBlockNamedBean); 080 _selectReporterNamedBean.copy(copy._selectReporterNamedBean); 081 copy.setOtherLocalVariable(_otherLocalVariable); 082 copy.setReference(_reference); 083 copy.setFormula(_formula); 084 _selectTable.copy(copy._selectTable); 085 copy.setListenToMemory(_listenToMemory); 086 copy.setListenToBlock(_listenToBlock); 087 copy.setListenToReporter(_listenToReporter); 088 return manager.registerAction(copy); 089 } 090 091 public LogixNG_SelectTable getSelectTableToSet() { 092 return _selectTableToSet; 093 } 094 095 public LogixNG_SelectNamedBean<Memory> getSelectMemoryNamedBean() { 096 return _selectMemoryNamedBean; 097 } 098 099 public LogixNG_SelectNamedBean<Block> getSelectBlockNamedBean() { 100 return _selectBlockNamedBean; 101 } 102 103 public LogixNG_SelectNamedBean<Reporter> getSelectReporterNamedBean() { 104 return _selectReporterNamedBean; 105 } 106 107 public void setVariableOperation(VariableOperation variableOperation) throws ParserException { 108 _variableOperation = variableOperation; 109 parseFormula(); 110 } 111 112 public VariableOperation getVariableOperation() { 113 return _variableOperation; 114 } 115 116 public LogixNG_SelectTable getSelectTable() { 117 return _selectTable; 118 } 119 120 public void setOtherLocalVariable(@Nonnull String localVariable) { 121 assertListenersAreNotRegistered(log, "setOtherLocalVariable"); 122 _otherLocalVariable = localVariable; 123 } 124 125 public String getOtherLocalVariable() { 126 return _otherLocalVariable; 127 } 128 129 public void setReference(@Nonnull String reference) { 130 assertListenersAreNotRegistered(log, "setReference"); 131 _reference = reference; 132 } 133 134 public String getReference() { 135 return _reference; 136 } 137 138 public void setConstantType(ConstantType constantType) { 139 _constantType = constantType; 140 } 141 142 public ConstantType getConstantType() { 143 return _constantType; 144 } 145 146 public void setConstantValue(String constantValue) { 147 _constantValue = constantValue; 148 } 149 150 public String getConstantValue() { 151 return _constantValue; 152 } 153 154 public void setFormula(String formula) throws ParserException { 155 _formula = formula; 156 parseFormula(); 157 } 158 159 public String getFormula() { 160 return _formula; 161 } 162 163 public void setListenToMemory(boolean listenToMemory) { 164 this._listenToMemory = listenToMemory; 165 } 166 167 public boolean getListenToMemory() { 168 return _listenToMemory; 169 } 170 171 public void setListenToBlock(boolean listenToBlock) { 172 this._listenToBlock = listenToBlock; 173 } 174 175 public boolean getListenToBlock() { 176 return _listenToBlock; 177 } 178 179 public void setListenToReporter(boolean listenToReporter) { 180 this._listenToReporter = listenToReporter; 181 } 182 183 public boolean getListenToReporter() { 184 return _listenToReporter; 185 } 186 187 private void parseFormula() throws ParserException { 188 if (_variableOperation == VariableOperation.CalculateFormula) { 189 Map<String, Variable> variables = new HashMap<>(); 190 191 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 192 _expressionNode = parser.parseExpression(_formula); 193 } else { 194 _expressionNode = null; 195 } 196 } 197 198 /** {@inheritDoc} */ 199 @Override 200 public Category getCategory() { 201 return Category.ITEM; 202 } 203 204 /** {@inheritDoc} */ 205 @Override 206 public void execute() throws JmriException { 207 208 final ConditionalNG conditionalNG = getConditionalNG(); 209 210 AtomicReference<JmriException> ref = new AtomicReference<>(); 211 212 ThreadingUtil.runOnLayoutWithJmriException(() -> { 213 214 Object value; 215 216 switch (_variableOperation) { 217 case SetToNull: 218 value = null; 219 break; 220 221 case SetToString: { 222 switch (_constantType) { 223 case String: 224 value = _constantValue; 225 break; 226 case Integer: 227 value = TypeConversionUtil.convertToLong(_constantValue); 228 break; 229 case FloatingNumber: 230 value = TypeConversionUtil.convertToDouble(_constantValue, true, true, true); 231 break; 232 case Boolean: 233 value = TypeConversionUtil.convertToBoolean(_constantValue, true); 234 break; 235 default: 236 // Throw exception 237 throw new IllegalArgumentException("_constantType has invalid value: {}" + _constantType.name()); 238 } 239 break; 240 } 241 242 case CopyVariableToVariable: 243 value = conditionalNG.getSymbolTable().getValue(_otherLocalVariable); 244 break; 245 246 case CopyMemoryToVariable: 247 Memory memory = _selectMemoryNamedBean.evaluateNamedBean(conditionalNG); 248 if (memory != null) { 249 value = memory.getValue(); 250 } else { 251 log.warn("ActionTable should copy memory to variable but memory is null"); 252 return; 253 } 254 break; 255 256 case CopyReferenceToVariable: 257 value = ReferenceUtil.getReference(conditionalNG.getSymbolTable(), 258 _reference); 259 break; 260 261 case CopyTableCellToVariable: 262 value = _selectTable.evaluateTableData(conditionalNG); 263 break; 264 265 case CopyBlockToVariable: 266 Block block = _selectBlockNamedBean.evaluateNamedBean(conditionalNG); 267 if (block != null) { 268 value = block.getValue(); 269 } else { 270 log.warn("ActionTable should copy block value to variable but block is null"); 271 return; 272 } 273 break; 274 275 case CopyReporterToVariable: 276 Reporter reporter = _selectReporterNamedBean.evaluateNamedBean(conditionalNG); 277 if (reporter != null) { 278 value = reporter.getCurrentReport(); 279 } else { 280 log.warn("ActionTable should copy current report to variable but reporter is null"); 281 return; 282 } 283 break; 284 285 case CalculateFormula: 286 if (_formula.isEmpty()) { 287 value = null; 288 } else { 289 if (_expressionNode == null) return; 290 291 value = _expressionNode.calculate(conditionalNG.getSymbolTable()); 292 } 293 break; 294 295 default: 296 // Throw exception 297 throw new IllegalArgumentException("_variableOperation has invalid value: {}" + _variableOperation.name()); 298 } 299 300 _selectTableToSet.evaluateAndSetTableData(conditionalNG, value); 301 }); 302 303 if (ref.get() != null) throw ref.get(); 304 } 305 306 @Override 307 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 308 throw new UnsupportedOperationException("Not supported."); 309 } 310 311 @Override 312 public int getChildCount() { 313 return 0; 314 } 315 316 @Override 317 public String getShortDescription(Locale locale) { 318 return Bundle.getMessage(locale, "ActionTable_Short"); 319 } 320 321 @Override 322 public String getLongDescription(Locale locale) { 323 324 String setTableName = _selectTableToSet.getTableNameDescription(locale); 325 String setRowName = _selectTableToSet.getTableRowDescription(locale); 326 String setColumnName = _selectTableToSet.getTableColumnDescription(locale); 327 String setTable = Bundle.getMessage(locale, "ActionTable_Table", setTableName, setRowName, setColumnName); 328 329 String copyToMemoryName = _selectMemoryNamedBean.getDescription(locale); 330 String copyToBlockName = _selectBlockNamedBean.getDescription(locale); 331 String copyToReporterName = _selectReporterNamedBean.getDescription(locale); 332 333 switch (_variableOperation) { 334 case SetToNull: 335 return Bundle.getMessage(locale, "ActionTable_Long_Null", setTable); 336 337 case SetToString: 338 return Bundle.getMessage(locale, "ActionTable_Long_Value", 339 setTable, _constantType._text, _constantValue); 340 341 case CopyVariableToVariable: 342 return Bundle.getMessage(locale, "ActionTable_Long_CopyVariableToVariable", 343 setTable, _otherLocalVariable); 344 345 case CopyMemoryToVariable: 346 return Bundle.getMessage(locale, "ActionTable_Long_CopyMemoryToVariable", 347 setTable, copyToMemoryName, Base.getListenString(_listenToMemory)); 348 349 case CopyReferenceToVariable: 350 return Bundle.getMessage(locale, "ActionTable_Long_CopyReferenceToVariable", 351 setTable, _reference); 352 353 case CopyBlockToVariable: 354 return Bundle.getMessage(locale, "ActionTable_Long_CopyBlockToVariable", 355 setTable, copyToBlockName, Base.getListenString(_listenToBlock)); 356 357 case CopyTableCellToVariable: 358 String tableName = _selectTable.getTableNameDescription(locale); 359 String rowName = _selectTable.getTableRowDescription(locale); 360 String columnName = _selectTable.getTableColumnDescription(locale); 361 return Bundle.getMessage(locale, "ActionTable_Long_CopyTableCellToVariable", 362 setTable, tableName, rowName, columnName); 363 364 case CopyReporterToVariable: 365 return Bundle.getMessage(locale, "ActionTable_Long_CopyReporterToVariable", 366 setTable, copyToReporterName, Base.getListenString(_listenToReporter)); 367 368 case CalculateFormula: 369 return Bundle.getMessage(locale, "ActionTable_Long_Formula", setTable, _formula); 370 371 default: 372 throw new IllegalArgumentException("_variableOperation has invalid value: " + _variableOperation.name()); 373 } 374 } 375 376 /** {@inheritDoc} */ 377 @Override 378 public void setup() { 379 // Do nothing 380 } 381 382 /** {@inheritDoc} */ 383 @Override 384 public void registerListenersForThisClass() { 385 if (!_listenersAreRegistered) { 386 if (_listenToMemory 387 && (_variableOperation == VariableOperation.CopyMemoryToVariable)) { 388 _selectMemoryNamedBean.addPropertyChangeListener("value", this); 389 } 390 if (_listenToBlock 391 && (_variableOperation == VariableOperation.CopyBlockToVariable)) { 392 _selectBlockNamedBean.addPropertyChangeListener("value", this); 393 } 394 if (_listenToReporter 395 && (_variableOperation == VariableOperation.CopyReporterToVariable)) { 396 _selectReporterNamedBean.addPropertyChangeListener("currentReport", this); 397 } 398 _selectMemoryNamedBean.registerListeners(); 399 _selectBlockNamedBean.registerListeners(); 400 _selectReporterNamedBean.registerListeners(); 401 _listenersAreRegistered = true; 402 } 403 } 404 405 /** {@inheritDoc} */ 406 @Override 407 public void unregisterListenersForThisClass() { 408 if (_listenersAreRegistered) { 409 if (_listenToMemory 410 && (_variableOperation == VariableOperation.CopyMemoryToVariable)) { 411 _selectMemoryNamedBean.removePropertyChangeListener("value", this); 412 } 413 if (_listenToBlock 414 && (_variableOperation == VariableOperation.CopyBlockToVariable)) { 415 _selectBlockNamedBean.removePropertyChangeListener("value", this); 416 } 417 if (_listenToReporter 418 && (_variableOperation == VariableOperation.CopyReporterToVariable)) { 419 _selectReporterNamedBean.removePropertyChangeListener("currentReport", this); 420 } 421 _selectMemoryNamedBean.unregisterListeners(); 422 _selectBlockNamedBean.unregisterListeners(); 423 _selectReporterNamedBean.unregisterListeners(); 424 _listenersAreRegistered = false; 425 } 426 } 427 428 /** {@inheritDoc} */ 429 @Override 430 public void propertyChange(PropertyChangeEvent evt) { 431 getConditionalNG().execute(); 432 } 433 434 /** {@inheritDoc} */ 435 @Override 436 public void disposeMe() { 437 } 438 439 440 public enum VariableOperation { 441 SetToNull(Bundle.getMessage("ActionTable_VariableOperation_SetToNull")), 442 SetToString(Bundle.getMessage("ActionTable_VariableOperation_SetToString")), 443 CopyVariableToVariable(Bundle.getMessage("ActionTable_VariableOperation_CopyVariableToVariable")), 444 CopyMemoryToVariable(Bundle.getMessage("ActionTable_VariableOperation_CopyMemoryToVariable")), 445 CopyReferenceToVariable(Bundle.getMessage("ActionTable_VariableOperation_CopyReferenceToVariable")), 446 CopyTableCellToVariable(Bundle.getMessage("ActionTable_VariableOperation_CopyTableCellToVariable")), 447 CopyBlockToVariable(Bundle.getMessage("ActionTable_VariableOperation_CopyBlockToVariable")), 448 CopyReporterToVariable(Bundle.getMessage("ActionTable_VariableOperation_CopyReporterToVariable")), 449 CalculateFormula(Bundle.getMessage("ActionTable_VariableOperation_CalculateFormula")); 450 451 private final String _text; 452 453 private VariableOperation(String text) { 454 this._text = text; 455 } 456 457 @Override 458 public String toString() { 459 return _text; 460 } 461 462 } 463 464 public enum ConstantType { 465 String(Bundle.getMessage("ActionTable_ConstantType_String")), 466 Integer(Bundle.getMessage("ActionTable_ConstantType_Integer")), 467 FloatingNumber(Bundle.getMessage("ActionTable_ConstantType_FloatingNumber")), 468 Boolean(Bundle.getMessage("ActionTable_ConstantType_Boolean")); 469 470 private final String _text; 471 472 private ConstantType(String text) { 473 this._text = text; 474 } 475 476 @Override 477 public String toString() { 478 return _text; 479 } 480 481 } 482 483 /** {@inheritDoc} */ 484 @Override 485 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 486 log.debug("getUsageReport :: ActionTable: bean = {}, report = {}", cdl, report); 487 _selectMemoryNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action); 488 _selectBlockNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action); 489 _selectReporterNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action); 490 } 491 492 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionTable.class); 493 494}