001package jmri.jmrit.logixng.util; 002 003import java.beans.*; 004import java.util.*; 005 006import javax.annotation.CheckForNull; 007import javax.annotation.Nonnull; 008 009import jmri.*; 010import jmri.jmrit.logixng.*; 011import jmri.jmrit.logixng.implementation.AbstractBase; 012import jmri.jmrit.logixng.util.parser.*; 013import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 014import jmri.util.TypeConversionUtil; 015 016/** 017 * Select namedBean for LogixNG actions and expressions. 018 * 019 * @param <E> the type of the named bean 020 * 021 * @author Daniel Bergqvist (C) 2022 022 */ 023public class LogixNG_SelectNamedBean<E extends NamedBean> implements VetoableChangeListener { 024 025 private final AbstractBase _base; 026 private final InUse _inUse; 027 private final Class<E> _class; 028 private final Manager<E> _manager; 029 private final LogixNG_SelectTable _selectTable; 030 private final PropertyChangeListener _listener; 031 private boolean _listenToMemory; 032 private boolean _listenersAreRegistered; 033 private boolean _onlyDirectAddressingAllowed; 034 035 private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct; 036 private NamedBeanHandle<E> _handle; 037 private String _reference = ""; 038 private NamedBeanHandle<Memory> _memoryHandle; 039 private String _localVariable = ""; 040 private String _formula = ""; 041 private ExpressionNode _expressionNode; 042 043 private String _delayedNamedBean; 044 045 046 public LogixNG_SelectNamedBean(AbstractBase base, Class<E> clazz, Manager<E> manager, PropertyChangeListener listener) { 047 _base = base; 048 _inUse = () -> true; 049 _class = clazz; 050 _manager = manager; 051 _selectTable = new LogixNG_SelectTable(_base, _inUse); 052 _listener = listener; 053 } 054 055 public LogixNG_SelectNamedBean(AbstractBase base, Class<E> clazz, Manager<E> manager, InUse inUse, PropertyChangeListener listener) { 056 _base = base; 057 _inUse = inUse; 058 _class = clazz; 059 _manager = manager; 060 _selectTable = new LogixNG_SelectTable(_base, _inUse); 061 _listener = listener; 062 } 063 064 public void setOnlyDirectAddressingAllowed() { 065 _onlyDirectAddressingAllowed = true; 066 } 067 068 public boolean getOnlyDirectAddressingAllowed() { 069 return _onlyDirectAddressingAllowed; 070 } 071 072 public void copy(LogixNG_SelectNamedBean<E> copy) throws ParserException { 073 copy.setAddressing(_addressing); 074 if (_handle != null) copy.setNamedBean(_handle); 075 copy.setLocalVariable(_localVariable); 076 copy.setReference(_reference); 077 copy.setMemory(_memoryHandle); 078 copy.setListenToMemory(_listenToMemory); 079 copy.setFormula(_formula); 080 _selectTable.copy(copy._selectTable); 081 } 082 083 public Manager<E> getManager() { 084 return _manager; 085 } 086 087 public void setAddressing(@Nonnull NamedBeanAddressing addressing) throws ParserException { 088 if (_onlyDirectAddressingAllowed && (addressing != NamedBeanAddressing.Direct)) { 089 throw new IllegalArgumentException("Addressing must be Direct"); 090 } 091 this._addressing = addressing; 092 parseFormula(); 093 } 094 095 public boolean isDirectAddressing() { 096 return _addressing == NamedBeanAddressing.Direct; 097 } 098 099 public NamedBeanAddressing getAddressing() { 100 return _addressing; 101 } 102 103 public void setDelayedNamedBean(@Nonnull String name) { 104 _delayedNamedBean = name; 105 } 106 107 public void setup() { 108 if (_delayedNamedBean != null) setNamedBean(_delayedNamedBean); 109 } 110 111 public void setNamedBean(@Nonnull String name) { 112 _base.assertListenersAreNotRegistered(log, "setNamedBean"); 113 E namedBean = _manager.getNamedBean(name); 114 if (namedBean != null) { 115 setNamedBean(name, namedBean); 116 } else { 117 removeNamedBean(); 118 log.error("{} \"{}\" is not found", _manager.getBeanTypeHandled(), name); 119 } 120 } 121 122 public void setNamedBean(@Nonnull NamedBeanHandle<E> handle) { 123 _base.assertListenersAreNotRegistered(log, "setNamedBean"); 124 _handle = handle; 125 _manager.addVetoableChangeListener(this); 126 } 127 128 public void setNamedBean(@Nonnull E namedBean) { 129 setNamedBean(namedBean.getDisplayName(), namedBean); 130 } 131 132 public void setNamedBean(@Nonnull String name, @Nonnull E namedBean) { 133 _base.assertListenersAreNotRegistered(log, "setNamedBean"); 134 setNamedBean(InstanceManager.getDefault(NamedBeanHandleManager.class) 135 .getNamedBeanHandle(name, namedBean)); 136 } 137 138 public void removeNamedBean() { 139 _base.assertListenersAreNotRegistered(log, "setNamedBean"); 140 if (_handle != null) { 141 _manager.removeVetoableChangeListener(this); 142 _handle = null; 143 } 144 } 145 146 public E getBean() { 147 if (_handle != null) { 148 return _handle.getBean(); 149 } else { 150 return null; 151 } 152 } 153 154 public NamedBeanHandle<E> getNamedBean() { 155 return _handle; 156 } 157 158 public E getNamedBeanIfDirectAddressing() { 159 if ((_handle != null) && (this._addressing == NamedBeanAddressing.Direct)) { 160 return _handle.getBean(); 161 } 162 return null; 163 } 164 165 public void setReference(@Nonnull String reference) { 166 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 167 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 168 } 169 _reference = reference; 170 } 171 172 public String getReference() { 173 return _reference; 174 } 175 176 public void setMemory(@Nonnull String memoryName) { 177 Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName); 178 if (memory != null) { 179 setMemory(memory); 180 } else { 181 removeMemory(); 182 log.warn("memory \"{}\" is not found", memoryName); 183 } 184 } 185 186 public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) { 187 _memoryHandle = handle; 188 InstanceManager.memoryManagerInstance().addVetoableChangeListener(this); 189 addRemoveVetoListener(); 190 } 191 192 public void setMemory(@Nonnull Memory memory) { 193 setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class) 194 .getNamedBeanHandle(memory.getDisplayName(), memory)); 195 } 196 197 public void removeMemory() { 198 if (_memoryHandle != null) { 199 _memoryHandle = null; 200 addRemoveVetoListener(); 201 } 202 } 203 204 public NamedBeanHandle<Memory> getMemory() { 205 return _memoryHandle; 206 } 207 208 public void setListenToMemory(boolean listenToMemory) { 209 _listenToMemory = listenToMemory; 210 } 211 212 public boolean getListenToMemory() { 213 return _listenToMemory; 214 } 215 216 public void setLocalVariable(@Nonnull String localVariable) { 217 _localVariable = localVariable; 218 } 219 220 public String getLocalVariable() { 221 return _localVariable; 222 } 223 224 public void setFormula(@Nonnull String formula) throws ParserException { 225 _formula = formula; 226 parseFormula(); 227 } 228 229 public String getFormula() { 230 return _formula; 231 } 232 233 private void parseFormula() throws ParserException { 234 if (_addressing == NamedBeanAddressing.Formula) { 235 Map<String, Variable> variables = new HashMap<>(); 236 237 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 238 _expressionNode = parser.parseExpression(_formula); 239 } else { 240 _expressionNode = null; 241 } 242 } 243 244 public LogixNG_SelectTable getSelectTable() { 245 return _selectTable; 246 } 247 248 private void addRemoveVetoListener() { 249 if (_memoryHandle != null) { 250 InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this); 251 } else { 252 InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this); 253 } 254 } 255 256 public E evaluateNamedBean(ConditionalNG conditionalNG) throws JmriException { 257 258 if (_addressing == NamedBeanAddressing.Direct) { 259 return _handle != null ? _handle.getBean() : null; 260 } else { 261 String name; 262 263 switch (_addressing) { 264 case Reference: 265 name = ReferenceUtil.getReference( 266 conditionalNG.getSymbolTable(), _reference); 267 break; 268 269 case LocalVariable: 270 SymbolTable symbolNamedBean = conditionalNG.getSymbolTable(); 271 name = TypeConversionUtil 272 .convertToString(symbolNamedBean.getValue(_localVariable), false); 273 break; 274 275 case Memory: 276 name = TypeConversionUtil 277 .convertToString(_memoryHandle.getBean().getValue(), false); 278 break; 279 280 case Formula: 281 name = _expressionNode != null 282 ? TypeConversionUtil.convertToString( 283 _expressionNode.calculate( 284 conditionalNG.getSymbolTable()), false) 285 : null; 286 break; 287 288 case Table: 289 name = TypeConversionUtil.convertToString( 290 _selectTable.evaluateTableData(conditionalNG), false); 291 break; 292 293 default: 294 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 295 } 296 297 E namedBean = null; 298 if (name != null) { 299 namedBean = _manager.getNamedBean(name); 300 } 301 return namedBean; 302 } 303 } 304 305 public String getDescription(Locale locale) { 306 String namedBean; 307 308 String memoryName; 309 if (_memoryHandle != null) { 310 memoryName = _memoryHandle.getName(); 311 } else { 312 memoryName = Bundle.getMessage(locale, "BeanNotSelected"); 313 } 314 315 switch (_addressing) { 316 case Direct: 317 String namedBeanName; 318 if (_handle != null) { 319 namedBeanName = _handle.getBean().getDisplayName(); 320 } else { 321 namedBeanName = Bundle.getMessage(locale, "BeanNotSelected"); 322 } 323 namedBean = Bundle.getMessage(locale, "AddressByDirect", namedBeanName); 324 break; 325 326 case Reference: 327 namedBean = Bundle.getMessage(locale, "AddressByReference", _reference); 328 break; 329 330 case Memory: 331 namedBean = Bundle.getMessage(locale, "AddressByMemory_Listen", memoryName, Base.getListenString(_listenToMemory)); 332 break; 333 334 case LocalVariable: 335 namedBean = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable); 336 break; 337 338 case Formula: 339 namedBean = Bundle.getMessage(locale, "AddressByFormula", _formula); 340 break; 341 342 case Table: 343 namedBean = Bundle.getMessage( 344 locale, 345 "AddressByTable", 346 _selectTable.getTableNameDescription(locale), 347 _selectTable.getTableRowDescription(locale), 348 _selectTable.getTableColumnDescription(locale)); 349 break; 350 351 default: 352 throw new IllegalArgumentException("invalid _addressing: " + _addressing.name()); 353 } 354 return namedBean; 355 } 356 357 /** 358 * Register listeners if this object needs that. 359 */ 360 public void registerListeners() { 361 if (!_listenersAreRegistered 362 && (_addressing == NamedBeanAddressing.Memory) 363 && (_memoryHandle != null) 364 && _listenToMemory) { 365 _memoryHandle.getBean().addPropertyChangeListener("value", _listener); 366 _listenersAreRegistered = true; 367 } 368 } 369 370 /** 371 * Unregister listeners if this object needs that. 372 */ 373 public void unregisterListeners() { 374 if (_listenersAreRegistered 375 && (_addressing == NamedBeanAddressing.Memory) 376 && (_memoryHandle != null) 377 && _listenToMemory) { 378 _memoryHandle.getBean().removePropertyChangeListener("value", _listener); 379 _listenersAreRegistered = false; 380 } 381 } 382 383 @Override 384 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 385 if ("CanDelete".equals(evt.getPropertyName()) && _inUse.isInUse()) { // No I18N 386 if (_inUse.isInUse() && (_class.isAssignableFrom(evt.getOldValue().getClass()))) { 387 if (evt.getOldValue().equals(getNamedBean().getBean())) { 388 PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null); 389 throw new PropertyVetoException(Bundle.getMessage("InUseVeto", _base.getDisplayName(), _base.getShortDescription()), e); 390 } 391 } 392 if (evt.getOldValue() instanceof Memory) { 393 boolean doVeto = false; 394 if ((_addressing == NamedBeanAddressing.Memory) && (_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) { 395 doVeto = true; 396 } 397 if (doVeto) { 398 PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null); 399 throw new PropertyVetoException(Bundle.getMessage("MemoryInUseMemoryExpressionVeto", _base.getDisplayName()), e); // NOI18N 400 } 401 } 402 } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N 403 if (_class.isAssignableFrom(evt.getOldValue().getClass())) { 404 if (evt.getOldValue().equals(getNamedBean().getBean())) { 405 removeNamedBean(); 406 } 407 } 408 if (evt.getOldValue() instanceof Memory) { 409 if ((_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) { 410 removeMemory(); 411 } 412 } 413 } 414 } 415 416 /** 417 * Add a {@link java.beans.PropertyChangeListener} for a specific property. 418 * 419 * @param listener The PropertyChangeListener to be added 420 */ 421 public void addPropertyChangeListener( 422 @CheckForNull PropertyChangeListener listener) { 423 if ((_addressing == NamedBeanAddressing.Direct) && (_handle != null)) { 424 _handle.getBean().addPropertyChangeListener(listener); 425 } 426 } 427 428 /** 429 * Add a {@link java.beans.PropertyChangeListener} for a specific property. 430 * 431 * @param propertyName The name of the property to listen on. 432 * @param listener The PropertyChangeListener to be added 433 */ 434 public void addPropertyChangeListener( 435 @CheckForNull String propertyName, 436 @CheckForNull PropertyChangeListener listener) { 437 if ((_addressing == NamedBeanAddressing.Direct) && (_handle != null)) { 438 _handle.getBean().addPropertyChangeListener(propertyName, listener); 439 } 440 } 441 442 /** 443 * Remove the specified listener of the specified property from this object. 444 * 445 * @param listener The {@link java.beans.PropertyChangeListener} to 446 * remove. 447 */ 448 public void removePropertyChangeListener( 449 @CheckForNull PropertyChangeListener listener) { 450 if (_handle != null) { 451 _handle.getBean().removePropertyChangeListener(listener); 452 } 453 } 454 455 /** 456 * Remove the specified listener of the specified property from this object. 457 * 458 * @param propertyName The name of the property to stop listening to. 459 * @param listener The {@link java.beans.PropertyChangeListener} to 460 * remove. 461 */ 462 public void removePropertyChangeListener( 463 @CheckForNull String propertyName, 464 @CheckForNull PropertyChangeListener listener) { 465 if (_handle != null) { 466 _handle.getBean().removePropertyChangeListener(propertyName, listener); 467 } 468 } 469 470 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl, Base base, Type type) { 471 log.debug("getUsageReport :: {}: bean = {}, report = {}", base.getShortDescription(), cdl, report); 472 if (_handle != null && bean.equals(_handle.getBean())) { 473 report.add(new NamedBeanUsageReport(type.toString(), cdl, base.getLongDescription())); 474 } 475 } 476 477 public static enum Type { 478 Action("LogixNGAction"), 479 Expression("LogixNGExpression"); 480 481 private final String _descr; 482 483 private Type(String descr) { 484 this._descr = descr; 485 } 486 487 @Override 488 public String toString() { 489 return _descr; 490 } 491 } 492 493 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNG_SelectNamedBean.class); 494}