001package jmri.jmrit.logixng.util; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.beans.PropertyVetoException; 006import java.beans.VetoableChangeListener; 007import java.util.HashMap; 008import java.util.Locale; 009import java.util.Map; 010 011import javax.annotation.Nonnull; 012 013import jmri.*; 014import jmri.jmrit.logixng.*; 015import jmri.jmrit.logixng.implementation.AbstractBase; 016import jmri.jmrit.logixng.util.parser.*; 017import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 018import jmri.util.TypeConversionUtil; 019 020/** 021 * Select a boolean for LogixNG actions and expressions. 022 * 023 * @author Daniel Bergqvist (C) 2023 024 */ 025public class LogixNG_SelectBoolean implements VetoableChangeListener { 026 027 private final AbstractBase _base; 028 private final InUse _inUse; 029 private final LogixNG_SelectTable _selectTable; 030 private final PropertyChangeListener _listener; 031 private boolean _listenToMemory; 032 private boolean _listenersAreRegistered; 033 034 private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct; 035 private boolean _value; 036 private String _reference = ""; 037 private NamedBeanHandle<Memory> _memoryHandle; 038 private String _localVariable = ""; 039 private String _formula = ""; 040 private ExpressionNode _expressionNode; 041 042 043 public LogixNG_SelectBoolean( 044 @Nonnull AbstractBase base, 045 @Nonnull PropertyChangeListener listener) { 046 _base = base; 047 _inUse = () -> true; 048 _selectTable = new LogixNG_SelectTable(_base, _inUse); 049 _listener = listener; 050 } 051 052 public void copy(LogixNG_SelectBoolean copy) throws ParserException { 053 copy.setAddressing(_addressing); 054 copy.setValue(_value); 055 copy.setLocalVariable(_localVariable); 056 copy.setReference(_reference); 057 copy.setMemory(_memoryHandle); 058 copy.setListenToMemory(_listenToMemory); 059 copy.setFormula(_formula); 060 _selectTable.copy(copy._selectTable); 061 } 062 063 public void setAddressing(@Nonnull NamedBeanAddressing addressing) throws ParserException { 064 this._addressing = addressing; 065 parseFormula(); 066 } 067 068 public boolean isDirectAddressing() { 069 return _addressing == NamedBeanAddressing.Direct; 070 } 071 072 public NamedBeanAddressing getAddressing() { 073 return _addressing; 074 } 075 076 public void setValue(boolean value) { 077 _base.assertListenersAreNotRegistered(log, "setEnum"); 078 _value = value; 079 } 080 081 public boolean getValue() { 082 return _value; 083 } 084 085 public void setReference(@Nonnull String reference) { 086 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 087 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 088 } 089 _reference = reference; 090 } 091 092 public String getReference() { 093 return _reference; 094 } 095 096 public void setMemory(@Nonnull String memoryName) { 097 Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName); 098 if (memory != null) { 099 setMemory(memory); 100 } else { 101 removeMemory(); 102 log.warn("memory \"{}\" is not found", memoryName); 103 } 104 } 105 106 public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) { 107 _memoryHandle = handle; 108 InstanceManager.memoryManagerInstance().addVetoableChangeListener(this); 109 addRemoveVetoListener(); 110 } 111 112 public void setMemory(@Nonnull Memory memory) { 113 setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class) 114 .getNamedBeanHandle(memory.getDisplayName(), memory)); 115 } 116 117 public void removeMemory() { 118 if (_memoryHandle != null) { 119 _memoryHandle = null; 120 addRemoveVetoListener(); 121 } 122 } 123 124 public NamedBeanHandle<Memory> getMemory() { 125 return _memoryHandle; 126 } 127 128 public void setListenToMemory(boolean listenToMemory) { 129 _listenToMemory = listenToMemory; 130 } 131 132 public boolean getListenToMemory() { 133 return _listenToMemory; 134 } 135 136 public void setLocalVariable(@Nonnull String localVariable) { 137 _localVariable = localVariable; 138 } 139 140 public String getLocalVariable() { 141 return _localVariable; 142 } 143 144 public void setFormula(@Nonnull String formula) throws ParserException { 145 _formula = formula; 146 parseFormula(); 147 } 148 149 public String getFormula() { 150 return _formula; 151 } 152 153 private void parseFormula() throws ParserException { 154 if (_addressing == NamedBeanAddressing.Formula) { 155 Map<String, Variable> variables = new HashMap<>(); 156 157 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 158 _expressionNode = parser.parseExpression(_formula); 159 } else { 160 _expressionNode = null; 161 } 162 } 163 164 public LogixNG_SelectTable getSelectTable() { 165 return _selectTable; 166 } 167 168 private void addRemoveVetoListener() { 169 if (_memoryHandle != null) { 170 InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this); 171 } else { 172 InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this); 173 } 174 } 175 176 public boolean evaluateValue(ConditionalNG conditionalNG) throws JmriException { 177 178 if (_addressing == NamedBeanAddressing.Direct) { 179 return _value; 180 } else { 181 Object val; 182 183 switch (_addressing) { 184 case Reference: 185 val = ReferenceUtil.getReference( 186 conditionalNG.getSymbolTable(), _reference); 187 break; 188 189 case Memory: 190 val = _memoryHandle.getBean().getValue(); 191 break; 192 193 case LocalVariable: 194 SymbolTable symbolNamedBean = conditionalNG.getSymbolTable(); 195 val = symbolNamedBean.getValue(_localVariable); 196 break; 197 198 case Formula: 199 val = _expressionNode != null 200 ? _expressionNode.calculate(conditionalNG.getSymbolTable()) 201 : null; 202 break; 203 204 case Table: 205 val = _selectTable.evaluateTableData(conditionalNG); 206 break; 207 208 default: 209 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 210 } 211 212 return TypeConversionUtil.convertToBoolean(val, false); 213 } 214 } 215 216 public String getDescription(Locale locale) { 217 String enumName; 218 219 String memoryName; 220 if (_memoryHandle != null) { 221 memoryName = _memoryHandle.getName(); 222 } else { 223 memoryName = Bundle.getMessage(locale, "BeanNotSelected"); 224 } 225 226 switch (_addressing) { 227 case Direct: 228 enumName = Bundle.getMessage(locale, "AddressByDirect", 229 _value ? Bundle.getMessage("Boolean_True") : Bundle.getMessage("Boolean_False")); 230 break; 231 232 case Reference: 233 enumName = Bundle.getMessage(locale, "AddressByReference", _reference); 234 break; 235 236 case Memory: 237 enumName = Bundle.getMessage(locale, "AddressByMemory_Listen", memoryName, Base.getListenString(_listenToMemory)); 238 break; 239 240 case LocalVariable: 241 enumName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable); 242 break; 243 244 case Formula: 245 enumName = Bundle.getMessage(locale, "AddressByFormula", _formula); 246 break; 247 248 case Table: 249 enumName = Bundle.getMessage( 250 locale, 251 "AddressByTable", 252 _selectTable.getTableNameDescription(locale), 253 _selectTable.getTableRowDescription(locale), 254 _selectTable.getTableColumnDescription(locale)); 255 break; 256 257 default: 258 throw new IllegalArgumentException("invalid _addressing: " + _addressing.name()); 259 } 260 return enumName; 261 } 262 263 /** 264 * Register listeners if this object needs that. 265 */ 266 public void registerListeners() { 267 if (!_listenersAreRegistered 268 && (_addressing == NamedBeanAddressing.Memory) 269 && (_memoryHandle != null) 270 && _listenToMemory) { 271 _memoryHandle.getBean().addPropertyChangeListener("value", _listener); 272 _listenersAreRegistered = true; 273 } 274 } 275 276 /** 277 * Unregister listeners if this object needs that. 278 */ 279 public void unregisterListeners() { 280 if (_listenersAreRegistered 281 && (_addressing == NamedBeanAddressing.Memory) 282 && (_memoryHandle != null) 283 && _listenToMemory) { 284 _memoryHandle.getBean().removePropertyChangeListener("value", _listener); 285 _listenersAreRegistered = false; 286 } 287 } 288 289 @Override 290 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 291 if ("CanDelete".equals(evt.getPropertyName()) && _inUse.isInUse()) { // No I18N 292 if (evt.getOldValue() instanceof Memory) { 293 boolean doVeto = false; 294 if ((_addressing == NamedBeanAddressing.Memory) && (_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) { 295 doVeto = true; 296 } 297 if (doVeto) { 298 PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null); 299 throw new PropertyVetoException(Bundle.getMessage("MemoryInUseMemoryExpressionVeto", _base.getDisplayName()), e); // NOI18N 300 } 301 } 302 } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N 303 if (evt.getOldValue() instanceof Memory) { 304 if (evt.getOldValue().equals(_memoryHandle.getBean())) { 305 removeMemory(); 306 } 307 } 308 } 309 } 310 311 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNG_SelectBoolean.class); 312}