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