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