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 enum value for LogixNG actions and expressions. 022 * 023 * @param <E> the type of enum 024 * 025 * @author Daniel Bergqvist (C) 2022 026 */ 027public class LogixNG_SelectEnum<E extends Enum<?>> implements VetoableChangeListener { 028 029 private final AbstractBase _base; 030 private final InUse _inUse; 031 private final E[] _enumArray; 032 private final LogixNG_SelectTable _selectTable; 033 private final PropertyChangeListener _listener; 034 private boolean _listenToMemory; 035 private boolean _listenersAreRegistered; 036 037 private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct; 038 private E _enum; 039 private String _reference = ""; 040 private NamedBeanHandle<Memory> _memoryHandle; 041 private String _localVariable = ""; 042 private String _formula = ""; 043 private ExpressionNode _expressionNode; 044 045 046 public LogixNG_SelectEnum(AbstractBase base, E[] enumArray, E initialEnum, PropertyChangeListener listener) { 047 _base = base; 048 _inUse = () -> true; 049 _enumArray = enumArray; 050 _enum = initialEnum; 051 _selectTable = new LogixNG_SelectTable(_base, _inUse); 052 _listener = listener; 053 } 054 055 056 public void copy(LogixNG_SelectEnum<E> copy) throws ParserException { 057 copy.setAddressing(_addressing); 058 copy.setEnum(_enum); 059 copy.setLocalVariable(_localVariable); 060 copy.setReference(_reference); 061 copy.setMemory(_memoryHandle); 062 copy.setListenToMemory(_listenToMemory); 063 copy.setFormula(_formula); 064 _selectTable.copy(copy._selectTable); 065 } 066 067 public void setAddressing(@Nonnull NamedBeanAddressing addressing) throws ParserException { 068 this._addressing = addressing; 069 parseFormula(); 070 } 071 072 public boolean isDirectAddressing() { 073 return _addressing == NamedBeanAddressing.Direct; 074 } 075 076 public NamedBeanAddressing getAddressing() { 077 return _addressing; 078 } 079 080 public void setEnum(@Nonnull E e) { 081 _base.assertListenersAreNotRegistered(log, "setEnum"); 082 _enum = e; 083 } 084 085 public boolean isEnum(E e) { 086 return _addressing == NamedBeanAddressing.Direct && _enum == e; 087 } 088 089 public E getEnum() { 090 return _enum; 091 } 092 093 public E getEnum(String name) { 094 for (E e : _enumArray) { 095 if (e.name().equals(name)) return e; 096 } 097 return null; 098 } 099 100 public void setReference(@Nonnull String reference) { 101 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 102 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 103 } 104 _reference = reference; 105 } 106 107 public String getReference() { 108 return _reference; 109 } 110 111 public void setMemory(@Nonnull String memoryName) { 112 Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName); 113 if (memory != null) { 114 setMemory(memory); 115 } else { 116 removeMemory(); 117 log.warn("memory \"{}\" is not found", memoryName); 118 } 119 } 120 121 public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) { 122 _memoryHandle = handle; 123 InstanceManager.memoryManagerInstance().addVetoableChangeListener(this); 124 addRemoveVetoListener(); 125 } 126 127 public void setMemory(@Nonnull Memory memory) { 128 setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class) 129 .getNamedBeanHandle(memory.getDisplayName(), memory)); 130 } 131 132 public void removeMemory() { 133 if (_memoryHandle != null) { 134 _memoryHandle = null; 135 addRemoveVetoListener(); 136 } 137 } 138 139 public NamedBeanHandle<Memory> getMemory() { 140 return _memoryHandle; 141 } 142 143 public void setListenToMemory(boolean listenToMemory) { 144 _listenToMemory = listenToMemory; 145 } 146 147 public boolean getListenToMemory() { 148 return _listenToMemory; 149 } 150 151 public void setLocalVariable(@Nonnull String localVariable) { 152 _localVariable = localVariable; 153 } 154 155 public String getLocalVariable() { 156 return _localVariable; 157 } 158 159 public void setFormula(@Nonnull String formula) throws ParserException { 160 _formula = formula; 161 parseFormula(); 162 } 163 164 public String getFormula() { 165 return _formula; 166 } 167 168 private void parseFormula() throws ParserException { 169 if (_addressing == NamedBeanAddressing.Formula) { 170 Map<String, Variable> variables = new HashMap<>(); 171 172 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 173 _expressionNode = parser.parseExpression(_formula); 174 } else { 175 _expressionNode = null; 176 } 177 } 178 179 public LogixNG_SelectTable getSelectTable() { 180 return _selectTable; 181 } 182 183 private void addRemoveVetoListener() { 184 if (_memoryHandle != null) { 185 InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this); 186 } else { 187 InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this); 188 } 189 } 190 191 public E evaluateEnum(ConditionalNG conditionalNG) throws JmriException { 192 193 if (_addressing == NamedBeanAddressing.Direct) { 194 return _enum; 195 } else { 196 String name; 197 198 switch (_addressing) { 199 case Reference: 200 name = ReferenceUtil.getReference( 201 conditionalNG.getSymbolTable(), _reference); 202 break; 203 204 case Memory: 205 name = TypeConversionUtil 206 .convertToString(_memoryHandle.getBean().getValue(), false); 207 break; 208 209 case LocalVariable: 210 SymbolTable symbolNamedBean = conditionalNG.getSymbolTable(); 211 name = TypeConversionUtil 212 .convertToString(symbolNamedBean.getValue(_localVariable), false); 213 break; 214 215 case Formula: 216 name = _expressionNode != null 217 ? TypeConversionUtil.convertToString( 218 _expressionNode.calculate( 219 conditionalNG.getSymbolTable()), false) 220 : null; 221 break; 222 223 case Table: 224 name = TypeConversionUtil.convertToString( 225 _selectTable.evaluateTableData(conditionalNG), false); 226 break; 227 228 default: 229 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 230 } 231 232 return getEnum(name); 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", _enum.toString()); 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_SelectEnum.class); 331}