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