001package jmri.jmrit.logixng.expressions; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.beans.VetoableChangeListener; 006import java.util.*; 007 008import javax.annotation.Nonnull; 009 010import jmri.*; 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 * Evaluates the state of a SignalHead. 021 * 022 * @author Daniel Bergqvist Copyright 2020 023 */ 024public class ExpressionSignalHead extends AbstractDigitalExpression 025 implements PropertyChangeListener, VetoableChangeListener { 026 027 private final LogixNG_SelectNamedBean<SignalHead> _selectNamedBean = 028 new LogixNG_SelectNamedBean<>( 029 this, SignalHead.class, InstanceManager.getDefault(SignalHeadManager.class), this); 030 031 private NamedBeanAddressing _queryAddressing = NamedBeanAddressing.Direct; 032 private QueryType _queryType = QueryType.Appearance; 033 private String _queryReference = ""; 034 private String _queryLocalVariable = ""; 035 private String _queryFormula = ""; 036 private ExpressionNode _queryExpressionNode; 037 038 private NamedBeanAddressing _appearanceAddressing = NamedBeanAddressing.Direct; 039 private int _signalHeadAppearance = SignalHead.DARK; 040 private String _appearanceReference = ""; 041 private String _appearanceLocalVariable = ""; 042 private String _appearanceFormula = ""; 043 private ExpressionNode _appearanceExpressionNode; 044 045 private final LogixNG_SelectNamedBean<SignalHead> _selectExampleNamedBean = 046 new LogixNG_SelectNamedBean<>( 047 this, SignalHead.class, InstanceManager.getDefault(SignalHeadManager.class), this); 048 049 050 public ExpressionSignalHead(String sys, String user) 051 throws BadUserNameException, BadSystemNameException { 052 super(sys, user); 053 } 054 055 @Override 056 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 057 DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class); 058 String sysName = systemNames.get(getSystemName()); 059 String userName = userNames.get(getSystemName()); 060 if (sysName == null) sysName = manager.getAutoSystemName(); 061 ExpressionSignalHead copy = new ExpressionSignalHead(sysName, userName); 062 copy.setComment(getComment()); 063 _selectNamedBean.copy(copy._selectNamedBean); 064 copy.setAppearance(_signalHeadAppearance); 065 copy.setQueryAddressing(_queryAddressing); 066 copy.setQueryType(_queryType); 067 copy.setQueryFormula(_queryFormula); 068 copy.setQueryLocalVariable(_queryLocalVariable); 069 copy.setQueryReference(_queryReference); 070 copy.setAppearanceAddressing(_appearanceAddressing); 071 copy.setAppearanceFormula(_appearanceFormula); 072 copy.setAppearanceLocalVariable(_appearanceLocalVariable); 073 copy.setAppearanceReference(_appearanceReference); 074 _selectExampleNamedBean.copy(copy._selectExampleNamedBean); 075 return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames); 076 } 077 078 public LogixNG_SelectNamedBean<SignalHead> getSelectNamedBean() { 079 return _selectNamedBean; 080 } 081 082 public LogixNG_SelectNamedBean<SignalHead> getSelectExampleNamedBean() { 083 return _selectExampleNamedBean; 084 } 085 086 public void setQueryAddressing(NamedBeanAddressing addressing) throws ParserException { 087 _queryAddressing = addressing; 088 parseQueryFormula(); 089 } 090 091 public NamedBeanAddressing getQueryAddressing() { 092 return _queryAddressing; 093 } 094 095 public void setQueryType(QueryType queryType) { 096 _queryType = queryType; 097 } 098 099 public QueryType getQueryType() { 100 return _queryType; 101 } 102 103 public void setQueryReference(@Nonnull String reference) { 104 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 105 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 106 } 107 _queryReference = reference; 108 } 109 110 public String getQueryReference() { 111 return _queryReference; 112 } 113 114 public void setQueryLocalVariable(@Nonnull String localVariable) { 115 _queryLocalVariable = localVariable; 116 } 117 118 public String getQueryLocalVariable() { 119 return _queryLocalVariable; 120 } 121 122 public void setQueryFormula(@Nonnull String formula) throws ParserException { 123 _queryFormula = formula; 124 parseQueryFormula(); 125 } 126 127 public String getQueryFormula() { 128 return _queryFormula; 129 } 130 131 private void parseQueryFormula() throws ParserException { 132 if (_queryAddressing == NamedBeanAddressing.Formula) { 133 Map<String, Variable> variables = new HashMap<>(); 134 135 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 136 _queryExpressionNode = parser.parseExpression(_queryFormula); 137 } else { 138 _queryExpressionNode = null; 139 } 140 } 141 142 public void setAppearanceAddressing(NamedBeanAddressing addressing) throws ParserException { 143 _appearanceAddressing = addressing; 144 parseAppearanceFormula(); 145 } 146 147 public NamedBeanAddressing getAppearanceAddressing() { 148 return _appearanceAddressing; 149 } 150 151 public void setAppearance(int appearance) { 152 _signalHeadAppearance = appearance; 153 } 154 155 public int getAppearance() { 156 return _signalHeadAppearance; 157 } 158 159 public void setAppearanceReference(@Nonnull String reference) { 160 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 161 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 162 } 163 _appearanceReference = reference; 164 } 165 166 public String getAppearanceReference() { 167 return _appearanceReference; 168 } 169 170 public void setAppearanceLocalVariable(@Nonnull String localVariable) { 171 _appearanceLocalVariable = localVariable; 172 } 173 174 public String getAppearanceLocalVariable() { 175 return _appearanceLocalVariable; 176 } 177 178 public void setAppearanceFormula(@Nonnull String formula) throws ParserException { 179 _appearanceFormula = formula; 180 parseAppearanceFormula(); 181 } 182 183 public String getAppearanceFormula() { 184 return _appearanceFormula; 185 } 186 187 private void parseAppearanceFormula() throws ParserException { 188 if (_appearanceAddressing == NamedBeanAddressing.Formula) { 189 Map<String, Variable> variables = new HashMap<>(); 190 191 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 192 _appearanceExpressionNode = parser.parseExpression(_appearanceFormula); 193 } else { 194 _appearanceExpressionNode = null; 195 } 196 } 197 198 /** {@inheritDoc} */ 199 @Override 200 public Category getCategory() { 201 return Category.ITEM; 202 } 203 204 private int getAppearanceFromName(String name, SignalHead signalHead) { 205 if (signalHead == null) throw new UnsupportedOperationException("_signalHeadHandle is null"); 206 207 String[] keys = signalHead.getValidStateKeys(); 208 for (int i=0; i < keys.length; i++) { 209 if (name.equals(keys[i])) return signalHead.getValidStates()[i]; 210 } 211 212 throw new IllegalArgumentException("Appearance "+name+" is not valid for signal head "+signalHead.getSystemName()); 213 } 214 215 private int getNewAppearance(ConditionalNG conditionalNG, SignalHead signalHead) 216 throws JmriException { 217 218 switch (_appearanceAddressing) { 219 case Direct: 220 return _signalHeadAppearance; 221 222 case Reference: 223 return getAppearanceFromName(ReferenceUtil.getReference( 224 conditionalNG.getSymbolTable(), _appearanceReference), signalHead); 225 226 case LocalVariable: 227 SymbolTable symbolTable = conditionalNG.getSymbolTable(); 228 return getAppearanceFromName(TypeConversionUtil 229 .convertToString(symbolTable.getValue(_appearanceLocalVariable), false), signalHead); 230 231 case Formula: 232 return _appearanceExpressionNode != null 233 ? getAppearanceFromName(TypeConversionUtil.convertToString( 234 _appearanceExpressionNode.calculate( 235 conditionalNG.getSymbolTable()), false), signalHead) 236 : -1; 237 238 default: 239 throw new IllegalArgumentException("invalid _aspectAddressing state: " + _appearanceAddressing.name()); 240 } 241 } 242 243 private QueryType getQuery(ConditionalNG conditionalNG) throws JmriException { 244 245 String oper = ""; 246 try { 247 switch (_queryAddressing) { 248 case Direct: 249 return _queryType; 250 251 case Reference: 252 oper = ReferenceUtil.getReference( 253 conditionalNG.getSymbolTable(), _queryReference); 254 return QueryType.valueOf(oper); 255 256 case LocalVariable: 257 SymbolTable symbolTable = 258 conditionalNG.getSymbolTable(); 259 oper = TypeConversionUtil 260 .convertToString(symbolTable.getValue(_queryLocalVariable), false); 261 return QueryType.valueOf(oper); 262 263 case Formula: 264 if (_appearanceExpressionNode != null) { 265 oper = TypeConversionUtil.convertToString( 266 _queryExpressionNode.calculate( 267 conditionalNG.getSymbolTable()), false); 268 return QueryType.valueOf(oper); 269 } else { 270 return null; 271 } 272 default: 273 throw new IllegalArgumentException("invalid _addressing state: " + _queryAddressing.name()); 274 } 275 } catch (IllegalArgumentException e) { 276 throw new JmriException("Unknown query: "+oper, e); 277 } 278 } 279 280 /** {@inheritDoc} */ 281 @Override 282 public boolean evaluate() throws JmriException { 283 final ConditionalNG conditionalNG = getConditionalNG(); 284 285 SignalHead signalHead = _selectNamedBean.evaluateNamedBean(conditionalNG); 286 287 if (signalHead == null) return false; 288 289 QueryType query = getQuery(conditionalNG); 290 291 boolean result = false; 292 int queryAppearance; 293 294 switch (query) { 295 case Appearance: 296 queryAppearance = getNewAppearance(conditionalNG, signalHead); 297 if (queryAppearance != -1) { 298 result = signalHead.getAppearance() == queryAppearance; 299 } 300 break; 301 case NotAppearance: 302 queryAppearance = getNewAppearance(conditionalNG, signalHead); 303 if (queryAppearance != -1) { 304 result = ! (signalHead.getAppearance() == queryAppearance); 305 } 306 break; 307 case Lit: 308 result = signalHead.getLit(); 309 break; 310 case NotLit: 311 result = ! signalHead.getLit(); 312 break; 313 case Held: 314 result = signalHead.getHeld(); 315 break; 316 case NotHeld: 317 result = ! signalHead.getHeld(); 318 break; 319 default: 320 throw new RuntimeException("Unknown enum: "+_queryType.name()); 321 } 322 323 return result; 324 } 325 326 @Override 327 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 328 throw new UnsupportedOperationException("Not supported."); 329 } 330 331 @Override 332 public int getChildCount() { 333 return 0; 334 } 335 336 @Override 337 public String getShortDescription(Locale locale) { 338 return Bundle.getMessage(locale, "SignalHead_Short"); 339 } 340 341 @Override 342 public String getLongDescription(Locale locale) { 343 String namedBean = _selectNamedBean.getDescription(locale); 344 String query; 345 String appearance; 346 347 switch (_queryAddressing) { 348 case Direct: 349 query = Bundle.getMessage(locale, "AddressByDirect", _queryType._text); 350 break; 351 352 case Reference: 353 query = Bundle.getMessage(locale, "AddressByReference", _queryReference); 354 break; 355 356 case LocalVariable: 357 query = Bundle.getMessage(locale, "AddressByLocalVariable", _queryLocalVariable); 358 break; 359 360 case Formula: 361 query = Bundle.getMessage(locale, "AddressByFormula", _queryFormula); 362 break; 363 364 default: 365 throw new IllegalArgumentException("invalid _queryAddressing state: " + _queryAddressing.name()); 366 } 367 368 switch (_appearanceAddressing) { 369 case Direct: 370 SignalHead signalHead = null; 371 if (_selectNamedBean.getAddressing() == NamedBeanAddressing.Direct) { 372 if (_selectNamedBean.getNamedBean() != null) { 373 signalHead = _selectNamedBean.getNamedBeanIfDirectAddressing(); 374 } 375 } else { 376 if (_selectExampleNamedBean.getNamedBean() != null) { 377 signalHead = _selectExampleNamedBean.getNamedBeanIfDirectAddressing(); 378 } 379 } 380 String a = ""; 381 if (signalHead != null) { 382 a = signalHead.getAppearanceName(_signalHeadAppearance); 383 } 384 appearance = Bundle.getMessage(locale, "AddressByDirect", a); 385 break; 386 387 case Reference: 388 appearance = Bundle.getMessage(locale, "AddressByReference", _appearanceReference); 389 break; 390 391 case LocalVariable: 392 appearance = Bundle.getMessage(locale, "AddressByLocalVariable", _appearanceLocalVariable); 393 break; 394 395 case Formula: 396 appearance = Bundle.getMessage(locale, "AddressByFormula", _appearanceFormula); 397 break; 398 399 default: 400 throw new IllegalArgumentException("invalid _stateAddressing state: " + _appearanceAddressing.name()); 401 } 402 403 if (_queryAddressing == NamedBeanAddressing.Direct) { 404 if (_queryType == QueryType.Appearance) { 405 return Bundle.getMessage(locale, "SignalHead_LongAppearance", namedBean, appearance); 406 } else if (_queryType == QueryType.NotAppearance) { 407 return Bundle.getMessage(locale, "SignalHead_LongNotAppearance", namedBean, appearance); 408 } else { 409 return Bundle.getMessage(locale, "SignalHead_Long", namedBean, query); 410 } 411 } else { 412 return Bundle.getMessage(locale, "SignalHead_LongUnknownOper", namedBean, query, appearance); 413 } 414 } 415 416 /** {@inheritDoc} */ 417 @Override 418 public void setup() { 419 // Do nothing 420 } 421 422 /** {@inheritDoc} */ 423 @Override 424 public void registerListenersForThisClass() { 425 SignalHead signalHead = _selectNamedBean.getNamedBeanIfDirectAddressing(); 426 427 if (!_listenersAreRegistered && (signalHead != null)) { 428 switch (_queryType) { 429 case Appearance: 430 case NotAppearance: 431 signalHead.addPropertyChangeListener("Appearance", this); 432 break; 433 434 case Lit: 435 case NotLit: 436 signalHead.addPropertyChangeListener("Lit", this); 437 break; 438 439 case Held: 440 case NotHeld: 441 signalHead.addPropertyChangeListener("Held", this); 442 break; 443 444 default: 445 throw new RuntimeException("Unknown enum: "+_queryType.name()); 446 } 447 _selectNamedBean.registerListeners(); 448 _listenersAreRegistered = true; 449 } 450 } 451 452 /** {@inheritDoc} */ 453 @Override 454 public void unregisterListenersForThisClass() { 455 SignalHead signalHead = _selectNamedBean.getNamedBeanIfDirectAddressing(); 456 457 if (_listenersAreRegistered && (signalHead != null)) { 458 switch (_queryType) { 459 case Appearance: 460 case NotAppearance: 461 signalHead.removePropertyChangeListener("Appearance", this); 462 break; 463 464 case Lit: 465 case NotLit: 466 signalHead.removePropertyChangeListener("Lit", this); 467 break; 468 469 case Held: 470 case NotHeld: 471 signalHead.removePropertyChangeListener("Held", this); 472 break; 473 474 default: 475 throw new RuntimeException("Unknown enum: "+_queryType.name()); 476 } 477 _selectNamedBean.unregisterListeners(); 478 _listenersAreRegistered = false; 479 } 480 } 481 482 /** {@inheritDoc} */ 483 @Override 484 public void propertyChange(PropertyChangeEvent evt) { 485 getConditionalNG().execute(); 486 } 487 488 /** {@inheritDoc} */ 489 @Override 490 public void disposeMe() { 491 } 492 493 494 495 public enum QueryType { 496 Appearance(Bundle.getMessage("SignalHeadQueryType_Appearance")), 497 NotAppearance(Bundle.getMessage("SignalHeadQueryType_NotAppearance")), 498 Lit(Bundle.getMessage("SignalHeadQueryType_Lit")), 499 NotLit(Bundle.getMessage("SignalHeadQueryType_NotLit")), 500 Held(Bundle.getMessage("SignalHeadQueryType_Held")), 501 NotHeld(Bundle.getMessage("SignalHeadQueryType_NotHeld")); 502 503 private final String _text; 504 505 private QueryType(String text) { 506 this._text = text; 507 } 508 509 @Override 510 public String toString() { 511 return _text; 512 } 513 514 } 515 516 /** {@inheritDoc} */ 517 @Override 518 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 519 log.debug("getUsageReport :: ExpressionSignalHead: bean = {}, report = {}", cdl, report); 520 _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action); 521 _selectExampleNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action); 522 } 523 524 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionSignalHead.class); 525 526}