001package jmri.jmrit.logixng.expressions; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.*; 006import java.util.regex.Matcher; 007import java.util.regex.Pattern; 008 009import javax.annotation.Nonnull; 010 011import jmri.*; 012import jmri.jmrit.logixng.*; 013import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean; 014import jmri.util.TypeConversionUtil; 015 016/** 017 * Evaluates the state of a Reporter. 018 * 019 * @author Daniel Bergqvist Copyright 2018 020 * @author Dave Sand Copyright 2021 021 */ 022public class ExpressionReporter extends AbstractDigitalExpression 023 implements PropertyChangeListener { 024 025 private final LogixNG_SelectNamedBean<Reporter> _selectNamedBean = 026 new LogixNG_SelectNamedBean<>( 027 this, Reporter.class, InstanceManager.getDefault(ReporterManager.class), this); 028 029 private final LogixNG_SelectNamedBean<Memory> _selectMemoryNamedBean = 030 new LogixNG_SelectNamedBean<>( 031 this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this); 032 033 private ReporterValue _reporterValue = ReporterValue.CurrentReport; 034 private ReporterOperation _reporterOperation = ReporterOperation.Equal; 035 private CompareTo _compareTo = CompareTo.Value; 036 037 private boolean _caseInsensitive = false; 038 private String _constantValue = ""; 039 private String _localVariable = ""; 040 private String _regEx = ""; 041 private boolean _listenToMemory = true; 042 043 public ExpressionReporter(String sys, String user) 044 throws BadUserNameException, BadSystemNameException { 045 super(sys, user); 046 } 047 048 @Override 049 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 050 DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class); 051 String sysName = systemNames.get(getSystemName()); 052 String userName = userNames.get(getSystemName()); 053 if (sysName == null) sysName = manager.getAutoSystemName(); 054 ExpressionReporter copy = new ExpressionReporter(sysName, userName); 055 copy.setComment(getComment()); 056 _selectNamedBean.copy(copy._selectNamedBean); 057 _selectMemoryNamedBean.copy(copy._selectMemoryNamedBean); 058 copy.setReporterValue(_reporterValue); 059 copy.setReporterOperation(_reporterOperation); 060 copy.setCompareTo(_compareTo); 061 copy.setCaseInsensitive(_caseInsensitive); 062 copy.setConstantValue(_constantValue); 063 copy.setListenToMemory(_listenToMemory); 064 return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames); 065 } 066 067 public LogixNG_SelectNamedBean<Reporter> getSelectNamedBean() { 068 return _selectNamedBean; 069 } 070 071 public LogixNG_SelectNamedBean<Memory> getSelectMemoryNamedBean() { 072 return _selectMemoryNamedBean; 073 } 074 075 public void setLocalVariable(@Nonnull String localVariable) { 076 assertListenersAreNotRegistered(log, "setLocalVariable"); 077 _localVariable = localVariable; 078 } 079 080 public String getLocalVariable() { 081 return _localVariable; 082 } 083 084 085 public void setConstantValue(String constantValue) { 086 _constantValue = constantValue; 087 } 088 089 public String getConstantValue() { 090 return _constantValue; 091 } 092 093 094 public void setRegEx(String regEx) { 095 _regEx = regEx; 096 } 097 098 public String getRegEx() { 099 return _regEx; 100 } 101 102 103 public void setListenToMemory(boolean listenToMemory) { 104 this._listenToMemory = listenToMemory; 105 } 106 107 public boolean getListenToMemory() { 108 return _listenToMemory; 109 } 110 111 112 public void setReporterValue(ReporterValue reporterValue) { 113 _reporterValue = reporterValue; 114 } 115 116 public ReporterValue getReporterValue() { 117 return _reporterValue; 118 } 119 120 121 public void setReporterOperation(ReporterOperation reporterOperation) { 122 _reporterOperation = reporterOperation; 123 } 124 125 public ReporterOperation getReporterOperation() { 126 return _reporterOperation; 127 } 128 129 130 public void setCompareTo(CompareTo compareTo) { 131 _compareTo = compareTo; 132 } 133 134 public CompareTo getCompareTo() { 135 return _compareTo; 136 } 137 138 139 public void setCaseInsensitive(boolean caseInsensitive) { 140 _caseInsensitive = caseInsensitive; 141 } 142 143 public boolean getCaseInsensitive() { 144 return _caseInsensitive; 145 } 146 147 148 /** {@inheritDoc} */ 149 @Override 150 public Category getCategory() { 151 return Category.ITEM; 152 } 153 154 private String getString(Object o) { 155 if (o != null) { 156 return o.toString(); 157 } 158 return null; 159 } 160 161 /** 162 * Compare two values using the comparator set using the comparison 163 * instructions in {@link #_reporterOperation}. 164 * 165 * <strong>Note:</strong> {@link #_reporterOperation} must be one of 166 * {@link #ExpressionReporter.ReporterOperation.LESS_THAN}, 167 * {@link #ExpressionReporter.ReporterOperation.LESS_THAN_OR_EQUAL}, 168 * {@link #ExpressionReporter.ReporterOperation.EQUAL}, 169 * {@link #ExpressionReporter.ReporterOperation.GREATER_THAN_OR_EQUAL}, 170 * or {@link #ExpressionReporter.ReporterOperation.GREATER_THAN}. 171 * 172 * @param value1 left side of the comparison 173 * @param value2 right side of the comparison 174 * @param caseInsensitive true if comparison should be case insensitive; 175 * false otherwise 176 * @return true if values compare per _reporterOperation; false otherwise 177 */ 178 private boolean compare(String value1, String value2, boolean caseInsensitive) { 179 if (value1 == null) { 180 return value2 == null; 181 } else { 182 if (value2 == null) { 183 return false; 184 } 185 value1 = value1.trim(); 186 value2 = value2.trim(); 187 } 188 try { 189 int n1 = Integer.parseInt(value1); 190 try { 191 int n2 = Integer.parseInt(value2); 192 log.debug("Compare numbers: n1= {} to n2= {}", n1, n2); 193 switch (_reporterOperation) // both are numbers 194 { 195 case LessThan: 196 return (n1 < n2); 197 case LessThanOrEqual: 198 return (n1 <= n2); 199 case Equal: 200 return (n1 == n2); 201 case NotEqual: 202 return (n1 != n2); 203 case GreaterThanOrEqual: 204 return (n1 >= n2); 205 case GreaterThan: 206 return (n1 > n2); 207 default: 208 throw new IllegalArgumentException("_reporterOperation has unknown value: "+_reporterOperation.name()); 209 } 210 } catch (NumberFormatException nfe) { 211 return _reporterOperation == ReporterOperation.NotEqual; // n1 is a number, n2 is not 212 } 213 } catch (NumberFormatException nfe) { 214 try { 215 Integer.parseInt(value2); 216 return _reporterOperation == ReporterOperation.NotEqual; // n1 is not a number, n2 is 217 } catch (NumberFormatException ex) { // OK neither a number 218 } 219 } 220 log.debug("Compare Strings: value1= {} to value2= {}", value1, value2); 221 int compare; 222 if (caseInsensitive) { 223 compare = value1.compareToIgnoreCase(value2); 224 } else { 225 compare = value1.compareTo(value2); 226 } 227 switch (_reporterOperation) { 228 case LessThan: 229 if (compare < 0) { 230 return true; 231 } 232 break; 233 case LessThanOrEqual: 234 if (compare <= 0) { 235 return true; 236 } 237 break; 238 case Equal: 239 if (compare == 0) { 240 return true; 241 } 242 break; 243 case NotEqual: 244 if (compare != 0) { 245 return true; 246 } 247 break; 248 case GreaterThanOrEqual: 249 if (compare >= 0) { 250 return true; 251 } 252 break; 253 case GreaterThan: 254 if (compare > 0) { 255 return true; 256 } 257 break; 258 default: 259 throw new IllegalArgumentException("_reporterOperation has unknown value: "+_reporterOperation.name()); 260 } 261 return false; 262 } 263 264 private boolean matchRegex(String reporterValue, String regex) { 265 Pattern pattern = Pattern.compile(regex); 266 Matcher m = pattern.matcher(reporterValue); 267 return m.matches(); 268 } 269 270 /** {@inheritDoc} */ 271 @Override 272 public boolean evaluate() throws JmriException { 273 Reporter reporter = _selectNamedBean.evaluateNamedBean(getConditionalNG()); 274 275 if (reporter == null) return false; 276 277 Object obj; 278 switch (_reporterValue) { 279 case CurrentReport: 280 obj = reporter.getCurrentReport(); 281 break; 282 283 case LastReport: 284 obj = reporter.getLastReport(); 285 break; 286 287 case State: 288 obj = reporter.getState(); 289 break; 290 291 default: 292 throw new IllegalArgumentException("_reporterValue has unknown value: "+_reporterValue.name()); 293 } 294 String reporterValue = getString(obj); 295 String otherValue = null; 296 boolean result; 297 298 switch (_compareTo) { 299 case Value: 300 otherValue = _constantValue; 301 break; 302 case Memory: 303 Memory memory = _selectMemoryNamedBean.evaluateNamedBean(getConditionalNG()); 304 otherValue = getString(memory.getValue()); 305 break; 306 case LocalVariable: 307 otherValue = TypeConversionUtil.convertToString(getConditionalNG().getSymbolTable().getValue(_localVariable), false); 308 break; 309 case RegEx: 310 // Do nothing 311 break; 312 default: 313 throw new IllegalArgumentException("_compareTo has unknown value: "+_compareTo.name()); 314 } 315 316 switch (_reporterOperation) { 317 case LessThan: 318 // fall through 319 case LessThanOrEqual: 320 // fall through 321 case Equal: 322 // fall through 323 case NotEqual: 324 // fall through 325 case GreaterThanOrEqual: 326 // fall through 327 case GreaterThan: 328 result = compare(reporterValue, otherValue, _caseInsensitive); 329 break; 330 331 case IsNull: 332 result = reporterValue == null; 333 break; 334 335 case IsNotNull: 336 result = reporterValue != null; 337 break; 338 339 case MatchRegex: 340 result = matchRegex(reporterValue, _regEx); 341 break; 342 343 case NotMatchRegex: 344 result = !matchRegex(reporterValue, _regEx); 345 break; 346 347 default: 348 throw new IllegalArgumentException("_reporterOperation has unknown value: "+_reporterOperation.name()); 349 } 350 351 return result; 352 } 353 354 @Override 355 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 356 throw new UnsupportedOperationException("Not supported."); 357 } 358 359 @Override 360 public int getChildCount() { 361 return 0; 362 } 363 364 @Override 365 public String getShortDescription(Locale locale) { 366 return Bundle.getMessage(locale, "Reporter_Short"); 367 } 368 369 @Override 370 public String getLongDescription(Locale locale) { 371 String reporterName = _selectNamedBean.getDescription(locale); 372 373 String memoryName = _selectMemoryNamedBean.getDescription(locale); 374 375 String message; 376 String other; 377 switch (_compareTo) { 378 case Value: 379 message = "Reporter_Long_CompareConstant"; 380 other = _constantValue; 381 break; 382 383 case Memory: 384 message = "Reporter_Long_CompareMemory"; 385 other = memoryName; 386 break; 387 388 case LocalVariable: 389 message = "Reporter_Long_CompareLocalVariable"; 390 other = _localVariable; 391 break; 392 393 case RegEx: 394 message = "Reporter_Long_CompareRegEx"; 395 other = _regEx; 396 break; 397 398 default: 399 throw new IllegalArgumentException("_compareTo has unknown value: "+_compareTo.name()); 400 } 401 402 switch (_reporterOperation) { 403 case LessThan: 404 // fall through 405 case LessThanOrEqual: 406 // fall through 407 case Equal: 408 // fall through 409 case NotEqual: 410 // fall through 411 case GreaterThanOrEqual: 412 // fall through 413 case GreaterThan: 414 return Bundle.getMessage(locale, message, reporterName, _reporterValue._text, _reporterOperation._text, other); 415 416 case IsNull: 417 // fall through 418 case IsNotNull: 419 return Bundle.getMessage(locale, "Reporter_Long_CompareNull", reporterName, _reporterValue._text, _reporterOperation._text); 420 421 case MatchRegex: 422 // fall through 423 case NotMatchRegex: 424 return Bundle.getMessage(locale, "Reporter_Long_CompareRegEx", reporterName, _reporterValue._text, _reporterOperation._text, other); 425 426 default: 427 throw new IllegalArgumentException("_reporterOperation has unknown value: "+_reporterOperation.name()); 428 } 429 } 430 431 /** {@inheritDoc} */ 432 @Override 433 public void setup() { 434 // Do nothing 435 } 436 437 /** {@inheritDoc} */ 438 @Override 439 public void registerListenersForThisClass() { 440 if (!_listenersAreRegistered) { 441 switch (_reporterValue) { 442 case CurrentReport: 443 _selectNamedBean.addPropertyChangeListener("currentReport", this); 444 break; 445 446 case LastReport: 447 _selectNamedBean.addPropertyChangeListener("lastReport", this); 448 break; 449 450 case State: 451 // No property change event is sent when state is changed for reports 452 break; 453 454 default: 455 // Do nothing 456 } 457 if (_listenToMemory) { 458 _selectMemoryNamedBean.addPropertyChangeListener("value", this); 459 } 460 _selectNamedBean.registerListeners(); 461 _listenersAreRegistered = true; 462 } 463 } 464 465 /** {@inheritDoc} */ 466 @Override 467 public void unregisterListenersForThisClass() { 468 if (_listenersAreRegistered) { 469 _selectNamedBean.removePropertyChangeListener("currentReport", this); 470 _selectNamedBean.removePropertyChangeListener("lastReport", this); 471 if (_listenToMemory) { 472 _selectMemoryNamedBean.removePropertyChangeListener("value", this); 473 } 474 _selectNamedBean.unregisterListeners(); 475 _listenersAreRegistered = false; 476 } 477 } 478 479 /** {@inheritDoc} */ 480 @Override 481 public void propertyChange(PropertyChangeEvent evt) { 482 getConditionalNG().execute(); 483 } 484 485 /** {@inheritDoc} */ 486 @Override 487 public void disposeMe() { 488 } 489 490 public enum ReporterValue { 491 CurrentReport(Bundle.getMessage("Reporter_Value_CurrentReport")), 492 LastReport(Bundle.getMessage("Reporter_Value_LastReport")), 493 State(Bundle.getMessage("Reporter_Value_State")); 494 495 private final String _text; 496 497 private ReporterValue(String text) { 498 this._text = text; 499 } 500 501 @Override 502 public String toString() { 503 return _text; 504 } 505 } 506 507 508 public enum ReporterOperation { 509 LessThan(Bundle.getMessage("ReporterOperation_LessThan"), true), 510 LessThanOrEqual(Bundle.getMessage("ReporterOperation_LessThanOrEqual"), true), 511 Equal(Bundle.getMessage("ReporterOperation_Equal"), true), 512 GreaterThanOrEqual(Bundle.getMessage("ReporterOperation_GreaterThanOrEqual"), true), 513 GreaterThan(Bundle.getMessage("ReporterOperation_GreaterThan"), true), 514 NotEqual(Bundle.getMessage("ReporterOperation_NotEqual"), true), 515 IsNull(Bundle.getMessage("ReporterOperation_IsNull"), false), 516 IsNotNull(Bundle.getMessage("ReporterOperation_IsNotNull"), false), 517 MatchRegex(Bundle.getMessage("ReporterOperation_MatchRegEx"), true), 518 NotMatchRegex(Bundle.getMessage("ReporterOperation_NotMatchRegEx"), true); 519 520 private final String _text; 521 private final boolean _extraValue; 522 523 private ReporterOperation(String text, boolean extraValue) { 524 this._text = text; 525 this._extraValue = extraValue; 526 } 527 528 @Override 529 public String toString() { 530 return _text; 531 } 532 533 public boolean hasExtraValue() { 534 return _extraValue; 535 } 536 537 } 538 539 540 public enum CompareTo { 541 Value(Bundle.getMessage("Reporter_CompareTo_Value")), 542 Memory(Bundle.getMessage("Reporter_CompareTo_Memory")), 543 LocalVariable(Bundle.getMessage("Reporter_CompareTo_LocalVariable")), 544 RegEx(Bundle.getMessage("Reporter_CompareTo_RegularExpression")); 545 546 private final String _text; 547 548 private CompareTo(String text) { 549 this._text = text; 550 } 551 552 @Override 553 public String toString() { 554 return _text; 555 } 556 557 } 558 559 /** {@inheritDoc} */ 560 @Override 561 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 562 log.debug("getUsageReport :: ExpressionReporter: bean = {}, report = {}", cdl, report); 563 _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Expression); 564 _selectMemoryNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Expression); 565 } 566 567 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionReporter.class); 568 569}