001package jmri.jmrit.logixng.actions; 002 003import jmri.jmrit.logixng.NamedBeanType; 004 005import java.beans.*; 006import java.util.*; 007 008import javax.annotation.Nonnull; 009 010import jmri.*; 011import jmri.jmrit.logixng.*; 012import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean; 013import jmri.jmrit.logixng.util.parser.ParserException; 014 015import net.jcip.annotations.GuardedBy; 016 017/** 018 * This action listens on some beans and runs the ConditionalNG on property change. 019 * 020 * @author Daniel Bergqvist Copyright 2019 021 */ 022public class ActionListenOnBeansTable extends AbstractDigitalAction 023 implements PropertyChangeListener, VetoableChangeListener { 024 025 private NamedBeanType _namedBeanType = NamedBeanType.Light; 026 private final LogixNG_SelectNamedBean<NamedTable> _selectNamedBean = 027 new LogixNG_SelectNamedBean<>( 028 this, NamedTable.class, InstanceManager.getDefault(NamedTableManager.class), this); 029 private TableRowOrColumn _tableRowOrColumn = TableRowOrColumn.Row; 030 private String _rowOrColumnName = ""; 031 private boolean _includeCellsWithoutHeader = false; 032 private boolean _listenOnAllProperties = false; 033 private final List<Map.Entry<NamedBean, String>> _namedBeansEntries = new ArrayList<>(); 034 private String _localVariableNamedBean; 035 private String _localVariableEvent; 036 private String _localVariableNewValue; 037 038 @GuardedBy("this") 039 private final Deque<PropertyChangeEvent> _eventQueue = new ArrayDeque<>(); 040 041 public ActionListenOnBeansTable(String sys, String user) 042 throws BadUserNameException, BadSystemNameException { 043 super(sys, user); 044 _selectNamedBean.setOnlyDirectAddressingAllowed(); 045 } 046 047 @Override 048 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException { 049 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 050 String sysName = systemNames.get(getSystemName()); 051 String userName = userNames.get(getSystemName()); 052 if (sysName == null) sysName = manager.getAutoSystemName(); 053 ActionListenOnBeansTable copy = new ActionListenOnBeansTable(sysName, userName); 054 copy.setComment(getComment()); 055 copy.setNamedBeanType(_namedBeanType); 056 _selectNamedBean.copy(copy._selectNamedBean); 057 copy.setTableRowOrColumn(_tableRowOrColumn); 058 copy.setRowOrColumnName(_rowOrColumnName); 059 copy.setIncludeCellsWithoutHeader(_includeCellsWithoutHeader); 060 061 copy.setLocalVariableNamedBean(_localVariableNamedBean); 062 copy.setLocalVariableEvent(_localVariableEvent); 063 copy.setLocalVariableNewValue(_localVariableNewValue); 064 065 for (var entry : _namedBeansEntries) { 066 copy._namedBeansEntries.add( 067 new HashMap.SimpleEntry<>(entry.getKey(), entry.getValue())); 068 } 069 070 return manager.registerAction(copy); 071 } 072 073 /** 074 * Get the type of the named beans 075 * @return the type of named beans 076 */ 077 public NamedBeanType getNamedBeanType() { 078 return _namedBeanType; 079 } 080 081 /** 082 * Set the type of the named beans 083 * @param namedBeanType the type of the named beans 084 */ 085 public void setNamedBeanType(@Nonnull NamedBeanType namedBeanType) { 086 if (namedBeanType == null) throw new RuntimeException("Daniel"); 087 _namedBeanType = namedBeanType; 088 } 089 090 public LogixNG_SelectNamedBean<NamedTable> getSelectNamedBean() { 091 return _selectNamedBean; 092 } 093 094 /** 095 * Get tableRowOrColumn. 096 * @return tableRowOrColumn 097 */ 098 public TableRowOrColumn getTableRowOrColumn() { 099 return _tableRowOrColumn; 100 } 101 102 /** 103 * Set tableRowOrColumn. 104 * @param tableRowOrColumn tableRowOrColumn 105 */ 106 public void setTableRowOrColumn(@Nonnull TableRowOrColumn tableRowOrColumn) { 107 _tableRowOrColumn = tableRowOrColumn; 108 } 109 110 /** 111 * Get name of row or column 112 * @return name of row or column 113 */ 114 public String getRowOrColumnName() { 115 return _rowOrColumnName; 116 } 117 118 /** 119 * Set name of row or column 120 * @param rowOrColumnName name of row or column 121 */ 122 public void setRowOrColumnName(@Nonnull String rowOrColumnName) { 123 if (rowOrColumnName == null) throw new IllegalArgumentException("Row/column name is null"); 124 _rowOrColumnName = rowOrColumnName; 125 } 126 127 public boolean getListenOnAllProperties() { 128 return _listenOnAllProperties; 129 } 130 131 public void setListenOnAllProperties(boolean listenOnAllProperties) { 132 _listenOnAllProperties = listenOnAllProperties; 133 } 134 135 /** 136 * Set whenever to include cells that doesn't have a header. 137 * Cells without headers can be used to use some cells in the table 138 * as comments. 139 * @return true if include cells that doesn't have a header, false otherwise 140 */ 141 public boolean getIncludeCellsWithoutHeader() { 142 return _includeCellsWithoutHeader; 143 } 144 145 /** 146 * Set whenever to include cells that doesn't have a header. 147 * Cells without headers can be used to use some cells in the table 148 * as comments. 149 * @param includeCellsWithoutHeader true if include rows/columns that 150 * doesn't have a header, false otherwise 151 */ 152 public void setIncludeCellsWithoutHeader(boolean includeCellsWithoutHeader) { 153 _includeCellsWithoutHeader = includeCellsWithoutHeader; 154 } 155 156 public void setLocalVariableNamedBean(String localVariableNamedBean) { 157 if ((localVariableNamedBean != null) && (!localVariableNamedBean.isEmpty())) { 158 this._localVariableNamedBean = localVariableNamedBean; 159 } else { 160 this._localVariableNamedBean = null; 161 } 162 } 163 164 public String getLocalVariableNamedBean() { 165 return _localVariableNamedBean; 166 } 167 168 public void setLocalVariableEvent(String localVariableEvent) { 169 if ((localVariableEvent != null) && (!localVariableEvent.isEmpty())) { 170 this._localVariableEvent = localVariableEvent; 171 } else { 172 this._localVariableEvent = null; 173 } 174 } 175 176 public String getLocalVariableEvent() { 177 return _localVariableEvent; 178 } 179 180 public void setLocalVariableNewValue(String localVariableNewValue) { 181 if ((localVariableNewValue != null) && (!localVariableNewValue.isEmpty())) { 182 this._localVariableNewValue = localVariableNewValue; 183 } else { 184 this._localVariableNewValue = null; 185 } 186 } 187 188 public String getLocalVariableNewValue() { 189 return _localVariableNewValue; 190 } 191 192 /** {@inheritDoc} */ 193 @Override 194 public Category getCategory() { 195 return Category.OTHER; 196 } 197 198 /** {@inheritDoc} */ 199 @Override 200 public void execute() { 201 // The purpose of this action is only to listen on property changes 202 // of the registered beans and execute the ConditionalNG when it 203 // happens. 204 205 String namedBean; 206 String event; 207 String newValue; 208 209 synchronized(this) { 210 PropertyChangeEvent evt = _eventQueue.poll(); 211 if (evt != null) { 212 namedBean = ((NamedBean)evt.getSource()).getDisplayName(); 213 event = evt.getPropertyName(); 214 newValue = evt.getNewValue() != null ? evt.getNewValue().toString() : null; 215 } else { 216 namedBean = null; 217 event = null; 218 newValue = null; 219 } 220 221 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 222 223 if (_localVariableNamedBean != null) { 224 symbolTable.setValue(_localVariableNamedBean, namedBean); 225 } 226 if (_localVariableEvent != null) { 227 symbolTable.setValue(_localVariableEvent, event); 228 } 229 if (_localVariableNewValue != null) { 230 symbolTable.setValue(_localVariableNewValue, newValue); 231 } 232 233 if (!_eventQueue.isEmpty()) { 234 getConditionalNG().execute(); 235 } 236 } 237 } 238 239 @Override 240 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 241 throw new UnsupportedOperationException("Not supported."); 242 } 243 244 @Override 245 public int getChildCount() { 246 return 0; 247 } 248 249 @Override 250 public String getShortDescription(Locale locale) { 251 return Bundle.getMessage(locale, "ActionListenOnBeansTable_Short"); 252 } 253 254 @Override 255 public String getLongDescription(Locale locale) { 256 String tableName = _selectNamedBean.getDescription(locale); 257 return Bundle.getMessage(locale, "ActionListenOnBeansTable_Long", 258 _namedBeanType.toString(), 259 _tableRowOrColumn.getOpposite().toStringLowerCase(), 260 _tableRowOrColumn.toStringLowerCase(), 261 _rowOrColumnName, 262 tableName); 263 } 264 265 /** {@inheritDoc} */ 266 @Override 267 public void setup() { 268 // Do nothing 269 } 270 271 public List<String> getItems() { 272 List<String> items = new ArrayList<>(); 273 274 if (_selectNamedBean.getNamedBean() == null) { 275 log.error("No table name is given"); 276 return items; // The list is empty 277 } 278 if (_rowOrColumnName.isEmpty()) { 279 log.error("rowOrColumnName is empty string"); 280 return items; // The list is empty 281 } 282 283 NamedTable table = _selectNamedBean.getNamedBean().getBean(); 284 285 if (_tableRowOrColumn == TableRowOrColumn.Row) { 286 int row = table.getRowNumber(_rowOrColumnName); 287 for (int column=1; column <= table.numColumns(); column++) { 288 // If the header is null or empty, treat the row as a comment 289 // unless _includeRowColumnWithoutHeader is true 290 Object header = table.getCell(0, column); 291// System.out.format("Row header: %s%n", header); 292 if (_includeCellsWithoutHeader 293 || ((header != null) && (!header.toString().isEmpty()))) { 294 Object cell = table.getCell(row, column); 295 if (cell != null) items.add(cell.toString()); 296 } 297 } 298 } else { 299 int column = table.getColumnNumber(_rowOrColumnName); 300 for (int row=1; row <= table.numRows(); row++) { 301 // If the header is null or empty, treat the row as a comment 302 // unless _includeRowColumnWithoutHeader is true 303 Object header = table.getCell(row, 0); 304// System.out.format("Column header: %s%n", header); 305 if (_includeCellsWithoutHeader 306 || ((header != null) && (!header.toString().isEmpty()))) { 307 Object cell = table.getCell(row, column); 308 if (cell != null && !cell.toString().isEmpty()) items.add(cell.toString()); 309 } 310 } 311 } 312 return items; 313 } 314 315 /** {@inheritDoc} */ 316 @Override 317 public void registerListenersForThisClass() { 318 if (_listenersAreRegistered) return; 319 320 List<String> items = getItems(); 321 322 for (String item : items) { 323 NamedBean namedBean = _namedBeanType.getManager().getNamedBean(item); 324 325 if (namedBean != null) { 326 Map.Entry<NamedBean, String> namedBeanEntry = 327 new HashMap.SimpleEntry<>(namedBean, _namedBeanType.getPropertyName()); 328 329 _namedBeansEntries.add(namedBeanEntry); 330 if (!_listenOnAllProperties 331 && (_namedBeanType.getPropertyName() != null)) { 332 namedBean.addPropertyChangeListener(_namedBeanType.getPropertyName(), this); 333 } else { 334 namedBean.addPropertyChangeListener(this); 335 } 336 } else { 337 log.warn("The named bean \"{}\" cannot be found in the manager for {}", item, _namedBeanType.toString()); 338 } 339 } 340 _selectNamedBean.registerListeners(); 341 _listenersAreRegistered = true; 342 } 343 344 /** {@inheritDoc} */ 345 @Override 346 public void unregisterListenersForThisClass() { 347 if (!_listenersAreRegistered) return; 348 349 for (Map.Entry<NamedBean, String> namedBeanEntry : _namedBeansEntries) { 350 if (!_listenOnAllProperties 351 && (namedBeanEntry.getValue() != null)) { 352 namedBeanEntry.getKey().removePropertyChangeListener(namedBeanEntry.getValue(), this); 353 } else { 354 namedBeanEntry.getKey().removePropertyChangeListener(this); 355 } 356 namedBeanEntry.getKey().removePropertyChangeListener(namedBeanEntry.getValue(), this); 357 } 358 _selectNamedBean.unregisterListeners(); 359 _listenersAreRegistered = false; 360 } 361 362 /** {@inheritDoc} */ 363 @Override 364 public void propertyChange(PropertyChangeEvent evt) { 365 boolean isQueueEmpty; 366 synchronized(this) { 367 isQueueEmpty = _eventQueue.isEmpty(); 368 _eventQueue.add(evt); 369 } 370 if (isQueueEmpty) { 371 getConditionalNG().execute(); 372 } 373 } 374 375 /** {@inheritDoc} */ 376 @Override 377 public void disposeMe() { 378 } 379 380 381 /** {@inheritDoc} */ 382 @Override 383 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 384/* 385 log.debug("getUsageReport :: ActionListenOnBeans: bean = {}, report = {}", cdl, report); 386 for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) { 387 if (namedBeanReference._handle != null) { 388 if (bean.equals(namedBeanReference._handle.getBean())) { 389 report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription())); 390 } 391 } 392 } 393*/ 394 } 395 396 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionListenOnBeansTable.class); 397 398}