001package jmri.jmrit.logixng.expressions; 002 003import java.beans.*; 004import java.util.*; 005 006import javax.annotation.Nonnull; 007 008import jmri.*; 009import jmri.Audio; 010import jmri.AudioManager; 011import jmri.jmrit.logixng.*; 012import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean; 013import jmri.jmrit.logixng.util.ReferenceUtil; 014import jmri.jmrit.logixng.util.parser.*; 015import jmri.jmrit.logixng.util.parser.ExpressionNode; 016import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 017import jmri.util.TypeConversionUtil; 018 019/** 020 * This expression evaluates the state of an Audio. 021 * 022 * @author Daniel Bergqvist Copyright 2023 023 */ 024public class ExpressionAudio extends AbstractDigitalExpression 025 implements PropertyChangeListener { 026 027 private final LogixNG_SelectNamedBean<Audio> _selectNamedBean = 028 new LogixNG_SelectNamedBean<>( 029 this, Audio.class, InstanceManager.getDefault(AudioManager.class)); 030 031 private boolean _hasChangedState = false; 032 033 private Is_IsNot_Enum _is_IsNot = Is_IsNot_Enum.Is; 034 035 private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct; 036 private AudioState _audioState = AudioState.Initial; 037 private String _stateReference = ""; 038 private String _stateLocalVariable = ""; 039 private String _stateFormula = ""; 040 private ExpressionNode _stateExpressionNode; 041 042 private boolean _checkOnlyOnChange; 043 044 045 public ExpressionAudio(String sys, String user) 046 throws BadUserNameException, BadSystemNameException { 047 super(sys, user); 048 } 049 050 @Override 051 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException { 052 DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class); 053 String sysName = systemNames.get(getSystemName()); 054 String userName = userNames.get(getSystemName()); 055 if (sysName == null) sysName = manager.getAutoSystemName(); 056 ExpressionAudio copy = new ExpressionAudio(sysName, userName); 057 copy.setComment(getComment()); 058 059 _selectNamedBean.copy(copy._selectNamedBean); 060 061 copy.set_Is_IsNot(_is_IsNot); 062 063 copy.setStateAddressing(_stateAddressing); 064 copy.setBeanState(_audioState); 065 copy.setStateReference(_stateReference); 066 copy.setStateLocalVariable(_stateLocalVariable); 067 copy.setStateFormula(_stateFormula); 068 069 copy.setCheckOnlyOnChange(_checkOnlyOnChange); 070 071 return manager.registerExpression(copy); 072 } 073 074 public LogixNG_SelectNamedBean<Audio> getSelectNamedBean() { 075 return _selectNamedBean; 076 } 077 078 public void set_Is_IsNot(Is_IsNot_Enum is_IsNot) { 079 _is_IsNot = is_IsNot; 080 } 081 082 public Is_IsNot_Enum get_Is_IsNot() { 083 return _is_IsNot; 084 } 085 086 087 public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException { 088 _stateAddressing = addressing; 089 parseStateFormula(); 090 } 091 092 public NamedBeanAddressing getStateAddressing() { 093 return _stateAddressing; 094 } 095 096 public void setBeanState(AudioState state) { 097 _audioState = state; 098 } 099 100 public AudioState getBeanState() { 101 return _audioState; 102 } 103 104 public void setStateReference(@Nonnull String reference) { 105 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 106 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 107 } 108 _stateReference = reference; 109 } 110 111 public String getStateReference() { 112 return _stateReference; 113 } 114 115 public void setStateLocalVariable(@Nonnull String localVariable) { 116 _stateLocalVariable = localVariable; 117 } 118 119 public String getStateLocalVariable() { 120 return _stateLocalVariable; 121 } 122 123 public void setStateFormula(@Nonnull String formula) throws ParserException { 124 _stateFormula = formula; 125 parseStateFormula(); 126 } 127 128 public String getStateFormula() { 129 return _stateFormula; 130 } 131 132 private void parseStateFormula() throws ParserException { 133 if (_stateAddressing == NamedBeanAddressing.Formula) { 134 Map<String, Variable> variables = new HashMap<>(); 135 136 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 137 _stateExpressionNode = parser.parseExpression(_stateFormula); 138 } else { 139 _stateExpressionNode = null; 140 } 141 } 142 143 144 public void setCheckOnlyOnChange(boolean triggerOnlyOnChange) { 145 _checkOnlyOnChange = triggerOnlyOnChange; 146 } 147 148 public boolean isCheckOnlyOnChange() { 149 return _checkOnlyOnChange; 150 } 151 152 153 /** {@inheritDoc} */ 154 @Override 155 public LogixNG_Category getCategory() { 156 return LogixNG_Category.ITEM; 157 } 158 159 private String getNewState() throws JmriException { 160 161 switch (_stateAddressing) { 162 case Reference: 163 return ReferenceUtil.getReference( 164 getConditionalNG().getSymbolTable(), _stateReference); 165 166 case LocalVariable: 167 SymbolTable symbolTable = 168 getConditionalNG().getSymbolTable(); 169 return TypeConversionUtil 170 .convertToString(symbolTable.getValue(_stateLocalVariable), false); 171 172 case Formula: 173 return _stateExpressionNode != null 174 ? TypeConversionUtil.convertToString( 175 _stateExpressionNode.calculate( 176 getConditionalNG().getSymbolTable()), false) 177 : null; 178 179 default: 180 throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name()); 181 } 182 } 183 184 /** {@inheritDoc} */ 185 @Override 186 public boolean evaluate() throws JmriException { 187 Audio audio = _selectNamedBean.evaluateNamedBean(getConditionalNG()); 188 189 if (audio == null) return false; 190 191 AudioState checkAudioState; 192 193 if ((_stateAddressing == NamedBeanAddressing.Direct)) { 194 checkAudioState = _audioState; 195 } else { 196 checkAudioState = AudioState.valueOf(getNewState()); 197 } 198 199 int currentState = audio.getState(); 200 201 if (_checkOnlyOnChange && !_hasChangedState) { 202 return false; 203 } 204 205 _hasChangedState = false; 206 207 if (_is_IsNot == Is_IsNot_Enum.Is) { 208 return currentState == checkAudioState.getID(); 209 } else { 210 return currentState != checkAudioState.getID(); 211 } 212 } 213 214 @Override 215 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 216 throw new UnsupportedOperationException("Not supported."); 217 } 218 219 @Override 220 public int getChildCount() { 221 return 0; 222 } 223 224 @Override 225 public String getShortDescription(Locale locale) { 226 return Bundle.getMessage(locale, "Audio_Short"); 227 } 228 229 @Override 230 public String getLongDescription(Locale locale) { 231 String namedBean = _selectNamedBean.getDescription(locale); 232 String state; 233 234 switch (_stateAddressing) { 235 case Direct: 236 state = Bundle.getMessage(locale, "AddressByDirect", _audioState._text); 237 break; 238 239 case Reference: 240 state = Bundle.getMessage(locale, "AddressByReference", _stateReference); 241 break; 242 243 case LocalVariable: 244 state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable); 245 break; 246 247 case Formula: 248 state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula); 249 break; 250 251 default: 252 throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name()); 253 } 254 255 256 if (_checkOnlyOnChange) { 257 return Bundle.getMessage(locale, "Audio_Long4", namedBean, _is_IsNot.toString(), state, Bundle.getMessage(locale, "Audio_CheckOnlyOnChange")); 258 } else { 259 return Bundle.getMessage(locale, "Audio_Long3", namedBean, _is_IsNot.toString(), state); 260 } 261 } 262 263 /** {@inheritDoc} */ 264 @Override 265 public void setup() { 266 // Do nothing 267 } 268 269 /** {@inheritDoc} */ 270 @Override 271 public void registerListenersForThisClass() { 272 if (!_listenersAreRegistered) { 273 _selectNamedBean.addPropertyChangeListener(this); 274 _listenersAreRegistered = true; 275 } 276 } 277 278 /** {@inheritDoc} */ 279 @Override 280 public void unregisterListenersForThisClass() { 281 if (_listenersAreRegistered) { 282 _selectNamedBean.removePropertyChangeListener(this); 283 _listenersAreRegistered = false; 284 } 285 } 286 287 /** {@inheritDoc} */ 288 @Override 289 public void propertyChange(PropertyChangeEvent evt) { 290 if (!Objects.equals(evt.getNewValue(), evt.getOldValue())) { 291 _hasChangedState = true; 292 } 293 getConditionalNG().execute(); 294 } 295 296 /** {@inheritDoc} */ 297 @Override 298 public void disposeMe() { 299 } 300 301 public enum AudioState { 302 Initial(Audio.STATE_INITIAL, Bundle.getMessage("Audio_StateInitial")), 303 Stopped(Audio.STATE_STOPPED, Bundle.getMessage("Audio_StateStopped")), 304 Playing(Audio.STATE_PLAYING, Bundle.getMessage("Audio_StatePlaying")), 305 Empty(Audio.STATE_EMPTY, Bundle.getMessage("Audio_StateEmpty")), 306 Loaded(Audio.STATE_LOADED, Bundle.getMessage("Audio_StateLoaded")), 307 Positioned(Audio.STATE_POSITIONED, Bundle.getMessage("Audio_StatePositioned")), 308 Moving(Audio.STATE_MOVING, Bundle.getMessage("Audio_StateMoving")); 309 310 private final int _id; 311 private final String _text; 312 313 private AudioState(int id, String text) { 314 this._id = id; 315 this._text = text; 316 } 317 318 public int getID() { 319 return _id; 320 } 321 322 @Override 323 public String toString() { 324 return _text; 325 } 326 } 327 328 /** {@inheritDoc} */ 329 @Override 330 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 331 log.debug("getUsageReport :: ExpressionAudio: bean = {}, report = {}", cdl, report); 332 _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Expression); 333 } 334 335 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionAudio.class); 336 337}