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