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