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 an integer for LogixNG actions and expressions. 022 * 023 * @author Daniel Bergqvist (C) 2022 024 */ 025public class LogixNG_SelectInteger 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 final FormatterParserValidator _formatterParserValidator; 034 035 private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct; 036 private int _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_SelectInteger( 045 @Nonnull AbstractBase base, 046 @Nonnull PropertyChangeListener listener) { 047 048 this(base, listener, new DefaultFormatterParserValidator()); 049 } 050 051 public LogixNG_SelectInteger( 052 @Nonnull AbstractBase base, 053 @Nonnull PropertyChangeListener listener, 054 @Nonnull FormatterParserValidator formatterParserValidator) { 055 _base = base; 056 _inUse = () -> true; 057 _selectTable = new LogixNG_SelectTable(_base, _inUse); 058 _listener = listener; 059 _formatterParserValidator = formatterParserValidator; 060 _value = _formatterParserValidator.getInitialValue(); 061 } 062 063 public void copy(LogixNG_SelectInteger copy) throws ParserException { 064 copy.setAddressing(_addressing); 065 copy.setValue(_value); 066 copy.setLocalVariable(_localVariable); 067 copy.setReference(_reference); 068 copy.setMemory(_memoryHandle); 069 copy.setListenToMemory(_listenToMemory); 070 copy.setFormula(_formula); 071 _selectTable.copy(copy._selectTable); 072 } 073 074 @Nonnull 075 public FormatterParserValidator getFormatterParserValidator() { 076 return _formatterParserValidator; 077 } 078 079 public void setAddressing(@Nonnull NamedBeanAddressing addressing) throws ParserException { 080 this._addressing = addressing; 081 parseFormula(); 082 } 083 084 public boolean isDirectAddressing() { 085 return _addressing == NamedBeanAddressing.Direct; 086 } 087 088 public NamedBeanAddressing getAddressing() { 089 return _addressing; 090 } 091 092 public void setValue(int value) { 093 _base.assertListenersAreNotRegistered(log, "setEnum"); 094 _value = value; 095 } 096 097 public int getValue() { 098 return _value; 099 } 100 101 public void setReference(@Nonnull String reference) { 102 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 103 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 104 } 105 _reference = reference; 106 } 107 108 public String getReference() { 109 return _reference; 110 } 111 112 public void setMemory(@Nonnull String memoryName) { 113 Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName); 114 if (memory != null) { 115 setMemory(memory); 116 } else { 117 removeMemory(); 118 log.warn("memory \"{}\" is not found", memoryName); 119 } 120 } 121 122 public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) { 123 _memoryHandle = handle; 124 InstanceManager.memoryManagerInstance().addVetoableChangeListener(this); 125 addRemoveVetoListener(); 126 } 127 128 public void setMemory(@Nonnull Memory memory) { 129 setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class) 130 .getNamedBeanHandle(memory.getDisplayName(), memory)); 131 } 132 133 public void removeMemory() { 134 if (_memoryHandle != null) { 135 _memoryHandle = null; 136 addRemoveVetoListener(); 137 } 138 } 139 140 public NamedBeanHandle<Memory> getMemory() { 141 return _memoryHandle; 142 } 143 144 public void setListenToMemory(boolean listenToMemory) { 145 _listenToMemory = listenToMemory; 146 } 147 148 public boolean getListenToMemory() { 149 return _listenToMemory; 150 } 151 152 public void setLocalVariable(@Nonnull String localVariable) { 153 _localVariable = localVariable; 154 } 155 156 public String getLocalVariable() { 157 return _localVariable; 158 } 159 160 public void setFormula(@Nonnull String formula) throws ParserException { 161 _formula = formula; 162 parseFormula(); 163 } 164 165 public String getFormula() { 166 return _formula; 167 } 168 169 private void parseFormula() throws ParserException { 170 if (_addressing == NamedBeanAddressing.Formula) { 171 Map<String, Variable> variables = new HashMap<>(); 172 173 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 174 _expressionNode = parser.parseExpression(_formula); 175 } else { 176 _expressionNode = null; 177 } 178 } 179 180 public LogixNG_SelectTable getSelectTable() { 181 return _selectTable; 182 } 183 184 private void addRemoveVetoListener() { 185 if (_memoryHandle != null) { 186 InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this); 187 } else { 188 InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this); 189 } 190 } 191 192 public int evaluateValue(ConditionalNG conditionalNG) throws JmriException { 193 194 if (_addressing == NamedBeanAddressing.Direct) { 195 return _value; 196 } else { 197 Object val; 198 199 switch (_addressing) { 200 case Reference: 201 val = ReferenceUtil.getReference( 202 conditionalNG.getSymbolTable(), _reference); 203 break; 204 205 case Memory: 206 val = _memoryHandle.getBean().getValue(); 207 break; 208 209 case LocalVariable: 210 SymbolTable symbolNamedBean = conditionalNG.getSymbolTable(); 211 val = symbolNamedBean.getValue(_localVariable); 212 break; 213 214 case Formula: 215 val = _expressionNode != null 216 ? _expressionNode.calculate(conditionalNG.getSymbolTable()) 217 : null; 218 break; 219 220 case Table: 221 val = _selectTable.evaluateTableData(conditionalNG); 222 break; 223 224 default: 225 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 226 } 227 228 if (val instanceof String) { 229 String validateResult = _formatterParserValidator.validate(val.toString()); 230 if (validateResult != null) throw new JmriException(validateResult); 231 return _formatterParserValidator.parse(val.toString()); 232 } 233 234 return (int) TypeConversionUtil.convertToLong(val, true, true); 235 } 236 } 237 238 public String getDescription(Locale locale) { 239 return getDescription(locale, true); 240 } 241 242 public String getDescription(Locale locale, boolean thousandsSeparator) { 243 String enumName; 244 245 String memoryName; 246 if (_memoryHandle != null) { 247 memoryName = _memoryHandle.getName(); 248 } else { 249 memoryName = Bundle.getMessage(locale, "BeanNotSelected"); 250 } 251 252 switch (_addressing) { 253 case Direct: 254 if (thousandsSeparator) { 255 enumName = Bundle.getMessage(locale, "AddressByDirect", _value); 256 } else { 257 enumName = Bundle.getMessage(locale, "AddressByDirect", Long.toString(_value)); 258 } 259 break; 260 261 case Reference: 262 enumName = Bundle.getMessage(locale, "AddressByReference", _reference); 263 break; 264 265 case Memory: 266 enumName = Bundle.getMessage(locale, "AddressByMemory_Listen", memoryName, Base.getListenString(_listenToMemory)); 267 break; 268 269 case LocalVariable: 270 enumName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable); 271 break; 272 273 case Formula: 274 enumName = Bundle.getMessage(locale, "AddressByFormula", _formula); 275 break; 276 277 case Table: 278 enumName = Bundle.getMessage( 279 locale, 280 "AddressByTable", 281 _selectTable.getTableNameDescription(locale), 282 _selectTable.getTableRowDescription(locale), 283 _selectTable.getTableColumnDescription(locale)); 284 break; 285 286 default: 287 throw new IllegalArgumentException("invalid _addressing: " + _addressing.name()); 288 } 289 return enumName; 290 } 291 292 /** 293 * Register listeners if this object needs that. 294 */ 295 public void registerListeners() { 296 if (!_listenersAreRegistered 297 && (_addressing == NamedBeanAddressing.Memory) 298 && (_memoryHandle != null) 299 && _listenToMemory) { 300 _memoryHandle.getBean().addPropertyChangeListener("value", _listener); 301 _listenersAreRegistered = true; 302 } 303 } 304 305 /** 306 * Unregister listeners if this object needs that. 307 */ 308 public void unregisterListeners() { 309 if (_listenersAreRegistered 310 && (_addressing == NamedBeanAddressing.Memory) 311 && (_memoryHandle != null) 312 && _listenToMemory) { 313 _memoryHandle.getBean().removePropertyChangeListener("value", _listener); 314 _listenersAreRegistered = false; 315 } 316 } 317 318 @Override 319 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 320 if ("CanDelete".equals(evt.getPropertyName()) && _inUse.isInUse()) { // No I18N 321 if (evt.getOldValue() instanceof Memory) { 322 boolean doVeto = false; 323 if ((_addressing == NamedBeanAddressing.Memory) && (_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) { 324 doVeto = true; 325 } 326 if (doVeto) { 327 PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null); 328 throw new PropertyVetoException(Bundle.getMessage("MemoryInUseMemoryExpressionVeto", _base.getDisplayName()), e); // NOI18N 329 } 330 } 331 } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N 332 if (evt.getOldValue() instanceof Memory) { 333 if (evt.getOldValue().equals(_memoryHandle.getBean())) { 334 removeMemory(); 335 } 336 } 337 } 338 } 339 340 341 /** 342 * Format, parse and validate. 343 */ 344 public interface FormatterParserValidator { 345 346 /** 347 * Get the initial value 348 * @return the initial value 349 */ 350 public int getInitialValue(); 351 352 /** 353 * Format the value 354 * @param value the value 355 * @return the formatted string 356 */ 357 public String format(int value); 358 359 /** 360 * Parse the string 361 * @param str the string 362 * @return the parsed value 363 */ 364 public int parse(String str); 365 366 /** 367 * Validates the string 368 * @param str the string 369 * @return null if valid. An error message if not valid 370 */ 371 public String validate(String str); 372 } 373 374 375 public static class DefaultFormatterParserValidator 376 implements FormatterParserValidator { 377 378 @Override 379 public int getInitialValue() { 380 return 0; 381 } 382 383 @Override 384 public String format(int value) { 385 return Integer.toString(value); 386 } 387 388 @Override 389 public int parse(String str) { 390 try { 391 return Integer.parseInt(str); 392 } catch (NumberFormatException e) { 393 return getInitialValue(); 394 } 395 } 396 397 @Override 398 public String validate(String str) { 399 try { 400 return null; 401 } catch (NumberFormatException e) { 402 return Bundle.getMessage("LogixNG_SelectInteger_MustBeValidInteger"); 403 } 404 } 405 406 } 407 408 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNG_SelectInteger.class); 409}