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